android 单元测试(4)Mockito与接口、抽象类、静态方法、kotlin、模板

1.官方文档

  mockit官网  https://site.mockito.org

  mockit api   https://javadoc.io/doc/org.mockito/mockito-core/latest/index.html

  mockit源码  https://github.com/mockito/mockito

  mockit教程  https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html

  kotlin扩展    https://github.com/mockito/mockito-kotlin

2.模拟接口、抽象类、内部抽象类

2.1 模拟接口

 1 interface Interface1              {          fun say() = "said" }
 2 interface Interface2 : Interface1 { override fun say() = "said" }
 3 
 4 @RunWith(AndroidJUnit4::class)
 5 class interface_test(){
 6     @Test @SmallTest //测试 interface
 7     fun test_mock_interface(){
 8         // mock creation
 9         val mock = Mockito.mock(Interface1::class.java)
10         // when
11         Mockito.`when`(mock.say()).thenReturn("mock said")
12         // then
13         assertTrue(mock.say() == "mock said")
14 
15         // spy
16         val spy = spy(Interface1::class.java)
17         Mockito.`when`(spy.say()).thenReturn("spy said")
18         assertTrue(spy.say() == "spy said")
19     }
20 }

2.2 模拟抽象类

  • 使用mock()并指定setting可以生成 任意抽象类 的模拟对象
  • 使用spy()无法对 构造有参数的抽象类 生成 模拟对象。
 1 abstract class Abstract1                    { fun say() = "said" }
 2 abstract class Abstract2(var name: String)  { fun say() = "said" }
 3 
 4 @RunWith(AndroidJUnit4::class)
 5 class Abstract_test(){
 6 
 7     @Test @SmallTest //测试无构造参数的 abstract 类
 8     fun test_mock_abstract(){
 9         // mock creation
10         val mock = Mockito.mock(Abstract1::class.java)
11         // when
12         Mockito.`when`(mock.say()).thenReturn("mock said")
13         var words = mock.say()
14         // then
15         Mockito.verify(mock).say()
16         assertTrue(words == "mock said")
17 
18         // spy
19         val setting = Mockito.withSettings().useConstructor().defaultAnswer(CALLS_REAL_METHODS)
20         val spy = spy(Abstract1::class.java)
21         Mockito.`when`(spy.say()).thenReturn("spy said")
22         words = spy.say()
23         Mockito.verify(spy).say()
24         assertTrue(words == "spy said")
25     }
26     @Test @SmallTest //测试有构造参数的 abstract 类,这时spy无法通过
27     fun test_mock_abstract_args(){
28         // spy
29         // val spy = spy(Abstract2::class.java) // error,无法spy
30         val setting = Mockito.withSettings().useConstructor("args").defaultAnswer(CALLS_REAL_METHODS)
31         val spy = Mockito.mock(Abstract2::class.java,setting)
32         Mockito.`when`(spy.say()).thenReturn("spy said")
33         val words = spy.say()
34         Mockito.verify(spy).say()
35         assertTrue(words == "spy said")
36     }
37     @Test @SmallTest //测试有构造参数的 inner abstract 类
38     fun test_mock_abstract_inner(){
39         abstract class Abstract3(var name: String)  { fun say() = "said" }
40         // spy
41         val setting = Mockito.withSettings().useConstructor("inner")/*.outerInstance(outerInstance)*/.defaultAnswer(CALLS_REAL_METHODS)
42         val spy = Mockito.mock(Abstract3::class.java,setting)
43         Mockito.`when`(spy.say()).thenReturn("inner said")
44         val words = spy.say()
45         Mockito.verify(spy).say()
46         assertTrue(words == "inner said")
47     }
48 }

3.打桩静态方法

3.1 要求

  • 使用 inline mock maker
  • java要使用 try-with-resources 语法,kotlin可使用use()

3.2 示例

java 代码 :

 1 @RunWith(AndroidJUnit4.class)
 2 public class MockitoJava {
 3     public static int value() { return -1;}
 4 
 5     @Test @SmallTest
 6     public void testMockStatic(){
 7         assertTrue(-1 == MockitoJava.value());
 8         try(MockedStatic mock = Mockito.mockStatic(MockitoJava.class)) {
 9             mock.when(MockitoJava::value).thenReturn(100);
10             assertTrue(100 == MockitoJava.value());
11             //超出范围后结束
12         }
13         assertTrue(-1 == MockitoJava.value());
14     }
15 }

kotlin 暂时不支持

 1     @Test @SmallTest
 2     fun test_mock_static2(){
 3         // given
 4         Mockito.mockStatic(StuStatic1::class.java).use {
 5             // when
 6             Mockito.`when`(StuStatic1.value1()).thenReturn(200  )
 7             // then
 8             assertTrue(StuStatic1.value1() == 200 )
 9 
10             it
11         }
12     }

4.临时打桩构造函数

  用Mockito.mockConstruction()临时打桩构造函数,在它的作用域内,new出来的对象都是模拟对象。

4.1 要求

  • 使用 inline mock maker
  • java要使用 try-with-resources 语句,kotlin可使用use()

4.2 java示例

 1 @RunWith(AndroidJUnit4.class)
 2 public class MockitoJava {
 3     @Test @SmallTest
 4     public void testConstruction(){
 5         class Student { public int run() { return 1;} }
 6 
 7         assertTrue(new Student().run() == 1 );
 8         try(MockedConstruction mocked = Mockito.mockConstruction(Student.class)){
 9             Student stu = new Student();
10             assertTrue(stu.run() == 0   );      //0,not 1
11             Mockito.when(stu.run()).thenReturn(10);
12             assertTrue(stu.run() == 10  );
13         }
14         assertTrue(new Student().run() == 1  ); //ok
15     }
16 }
  • 第8-13行是个临时作用域,使用了java的try-with-resources 语法
  • 使用Mockito.mockConstruction生成了一个MockedConstruction对象。
  • 在第8-13行内,new 的student对象都是模拟对象,且可打桩(第11行)。
  • 第10行模拟对象默认是返回0值的。

4.3 kotlin示例

 1     @Test @SmallTest
 2     fun test_mocking_construction(){
 3         // given
 4         open class Student{ fun run() = 1 fun stop() = 2}
 5 
 6         Mockito.mockConstruction(Student::class.java).use {
 7             val student = Student()
 8             assertTrue(student.run() == 0 )  //ok
 9             `when`(student.run()).thenReturn(10)
10             assertTrue(student.run() == 10)
11         }
12         assertTrue(Student().run  () == 10)  //failed
13     }

  结果同4.2

5.kotlin 拓展库

  源码:  https://github.com/mockito/mockito-kotlin  

  示例:  https://github.com/mockito/mockito-kotlin/tree/main/tests/src/test/kotlin/test

5.1 引入扩展库

    testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0"

  扩展库最新版本可在github上找,不是在mockito官网上.它可能比mockito库更新的慢。

5.3 简单示例

 1 @RunWith(AndroidJUnit4::class)
 2 class MockitoKotlin {
 3     @Test @SmallTest
 4     fun mockito_kotlin_test_mock(){
 5         open class Student{ fun value() = 1}
 6         val mock = mock<Student>{
 7             on { value() } doReturn(10)
 8         }
 9         assertTrue(mock.value() == 10)
10 
11         val spy = spy<Student>{
12             on(it.value()).thenReturn(10)
13         }
14         assertTrue(spy.value() == 10)
15         Mockito.reset(spy)
16         assertTrue(spy.value() == 1 )
17     }
18     @Test @SmallTest
19     fun mockito_kotlin_test_doNothing(){
20         open class Student(var v : Int = -1) {
21             fun value(){
22                 println("kotlin doNothing")
23             }
24         }
25         val mock = mock<Student> {
26             doNothing().`when`(it).value()
27         }
28         mock.value()
29         verify(mock).value()
30     }
31 }

   使用 on .. thenReturn 对应 when...thenReturn

5.4 更多示例

5.4.1 mock

 1     @Test
 2     fun propertyClassVariable() {
 3         /* When */
 4         propertyClassVariable = mock()
 5 
 6         /* Then */
 7         expect(propertyClassVariable).toNotBeNull()
 8     }
 9 
10     @Test
11     fun untypedVariable() {
12         /* When */
13         val instance = mock<MyClass>()
14 
15         expect(instance).toNotBeNull()
16     }
17 
18     @Test
19     fun deepStubs() {
20         val cal: Calendar = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
21         whenever(cal.time.time).thenReturn(123L)
22         expect(cal.time.time).toBe(123L)
23     }
24 
25 
26     @Test
27     fun testMockStubbing_lambda() {
28         /* Given */
29         val mock = mock<Open>() {
30             on { stringResult() } doReturn "A"
31         }
32 
33         /* When */
34         val result = mock.stringResult()
35 
36         /* Then */
37         expect(result).toBe("A")
38     }

5.4.2 spy

 1     @Test
 2     fun spyInterfaceInstance() {
 3         /* When */
 4         val result = spy(interfaceInstance)
 5 
 6         /* Then */
 7         expect(result).toNotBeNull()
 8     }
 9 
10     @Test
11     fun spyOpenClassInstance() {
12         /* When */
13         val result = spy(openClassInstance)
14 
15         /* Then */
16         expect(result).toNotBeNull()
17     }
18 
19     @Test
20     fun doReturnWithSpy() {
21         val date = spy(Date())
22         doReturn(123L).whenever(date).time
23         expect(date.time).toBe(123L)
24     }
25 
26     @Test
27     fun doNothingWithSpy() {
28         val date = spy(Date(0))
29         doNothing().whenever(date).time = 5L
30         date.time = 5L
31         expect(date.time).toBe(0L)
32     }
33 
34     @Test(expected = IllegalArgumentException::class)
35     fun doThrowWithSpy() {
36         val date = spy(Date(0))
37         doThrow(IllegalArgumentException()).whenever(date).time
38         date.time
39     }
40 
41     @Test
42     fun doCallRealMethodWithSpy() {
43         val date = spy(Date(0))
44         doReturn(123L).whenever(date).time
45         doCallRealMethod().whenever(date).time
46         expect(date.time).toBe(0L)
47     }
48 
49     @Test
50     fun doReturnWithDefaultInstanceSpyStubbing() {
51         val timeVal = 12L
52 
53         val dateSpy = spy<Date> {
54             on { time } doReturn timeVal
55         }
56 
57         expect(dateSpy.time).toBe(timeVal)
58     }
59 
60     @Test
61     fun doReturnWithSpyStubbing() {
62         val timeVal = 15L
63 
64         val dateSpy = spy(Date(0)) {
65             on { time } doReturn timeVal
66         }
67 
68         expect(dateSpy.time).toBe(timeVal)
69     }
70 
71     @Test
72     fun passAnyStringToSpy() {
73         /* Given */
74         val my = spy(MyClass())
75 
76         /* When */
77         doReturn("mocked").whenever(my).foo(any())
78 
79         /* Then */
80         expect(my.foo("hello")).toBe("mocked")
81     }

5.4.3 Matchers

 1     @Test
 2     fun anyString() {
 3         mock<Methods>().apply {
 4             string("")
 5             verify(this).string(any())
 6         }
 7     }
 8 
 9     @Test
10     fun anyInt() {
11         mock<Methods>().apply {
12             int(3)
13             verify(this).int(any())
14         }
15     }
16 
17     @Test
18     fun anyClosedClass() {
19         mock<Methods>().apply {
20             closed(Closed())
21             verify(this).closed(any())
22         }
23     }
24 
25     @Test
26     fun anyIntArray() {
27         mock<Methods>().apply {
28             intArray(intArrayOf())
29             verify(this).intArray(any())
30         }
31     }
32 
33     @Test
34     fun anyClassArray() {
35         mock<Methods>().apply {
36             closedArray(arrayOf(Closed()))
37             verify(this).closedArray(anyArray())
38         }
39     }
40 
41     @Test
42     fun anyNullableClassArray() {
43         mock<Methods>().apply {
44             closedNullableArray(arrayOf(Closed(), null))
45             verify(this).closedNullableArray(anyArray())
46         }
47     }
48 
49     @Test
50     fun anyStringVararg() {
51         mock<Methods>().apply {
52             closedVararg(Closed(), Closed())
53             verify(this).closedVararg(anyVararg())
54         }
55     }
56 
57     @Test
58     fun anyNull_neverVerifiesAny() {
59         mock<Methods>().apply {
60             nullableString(null)
61             verify(this, never()).nullableString(any())
62         }
63     }
64 
65     @Test
66     fun anyNull_verifiesAnyOrNull() {
67         mock<Methods>().apply {
68             nullableString(null)
69             verify(this).nullableString(anyOrNull())
70         }
71     }
72 
73     @Test
74     fun anyNull_forPrimitiveBoolean() {
75         mock<Methods>().apply {
76             boolean(false)
77             verify(this).boolean(anyOrNull())
78         }
79     }
80     @Test
81     fun anyNull_forPrimitiveByte() {
82         mock<Methods>().apply {
83             byte(3)
84             verify(this).byte(anyOrNull())
85         }
86     }

5.4.4 eq()

 1     @Test
 2     fun eqOpenClassInstance() {
 3         /* When */
 4         val result = eq(openClassInstance)
 5 
 6         /* Then */
 7         expect(result).toNotBeNull()
 8     }
 9 
10     @Test
11     fun eqClosedClassInstance() {
12         /* When */
13         val result = eq(closedClassInstance)
14 
15         /* Then */
16         expect(result).toNotBeNull()
17     }
18 
19     @Test
20     fun nullArgument() {
21         /* Given */
22         val s: String? = null
23 
24         /* When */
25         val result = eq(s)
26 
27         /* Then */
28         expect(result).toBeNull()
29     }

5.4.5 verfity()

 1     @Test
 2     fun verify0Calls() {
 3         val iface = mock<TestInterface>()
 4 
 5         verify(iface) {
 6             0 * { call(any()) }
 7         }
 8     }
 9 
10     @Test
11     fun verifyNCalls() {
12         val iface = mock<TestInterface>()
13 
14         iface.call(42)
15         iface.call(42)
16 
17         verify(iface) {
18             2 * { call(42) }
19         }
20     }
21 
22     @Test(expected = TooFewActualInvocations::class)
23     fun verifyFailsWithWrongCount() {
24         val iface = mock<TestInterface>()
25 
26         iface.call(0)
27 
28         verify(iface) {
29             2 * { call(0) }
30         }
31     }
32 
33     @Test(expected = ArgumentsAreDifferent::class)
34     fun verifyFailsWithWrongArg() {
35         val iface = mock<TestInterface>()
36 
37         iface.call(3)
38 
39         verify(iface) {
40             1 * { call(0) }
41         }
42     }
43 
44     @Test
45     fun verifyDefaultArgs_firstParameter() {
46         /* Given */
47         val m = mock<TestInterface>()
48 
49         /* When */
50         m.defaultArgs(a = 2)
51 
52         /* Then */
53         verify(m).defaultArgs(2)
54     }
55 
56     @Test
57     fun verifyDefaultArgs_secondParameter() {
58         /* Given */
59         val m = mock<TestInterface>()
60 
61         /* When */
62         m.defaultArgs(b = 2)
63 
64         /* Then */
65         verify(m).defaultArgs(b = 2)
66     }

5.4.6 ArgumentCaptor

 1     @Test
 2     fun argumentCaptor_destructuring2() {
 3         /* Given */
 4         val date: Date = mock()
 5 
 6         /* When */
 7         date.time = 5L
 8 
 9         /* Then */
10         val (captor1, captor2) = argumentCaptor<Long, Long>()
11         verify(date).time = captor1.capture()
12         verify(date).time = captor2.capture()
13         expect(captor1.lastValue).toBe(5L)
14         expect(captor2.lastValue).toBe(5L)
15     }
16 
17     @Test
18     fun argumentCaptor_destructuring3() {
19         /* Given */
20         val date: Date = mock()
21 
22         /* When */
23         date.time = 5L
24 
25         /* Then */
26         val (captor1, captor2, captor3) = argumentCaptor<Long, Long, Long>()
27         val verifyCaptor: KArgumentCaptor<Long>.() -> Unit = {
28             verify(date).time = capture()
29             expect(lastValue).toBe(5L)
30         }
31         captor1.apply(verifyCaptor)
32         captor2.apply(verifyCaptor)
33         captor3.apply(verifyCaptor)
34     }
35 
36     @Test
37     fun argumentCaptor_destructuring4() {
38         /* Given */
39         val date: Date = mock()
40 
41         /* When */
42         date.time = 5L
43 
44         /* Then */
45         val (captor1, captor2, captor3, captor4) = argumentCaptor<Long, Long, Long, Long>()
46         val verifyCaptor: KArgumentCaptor<Long>.() -> Unit = {
47             verify(date).time = capture()
48             expect(lastValue).toBe(5L)
49         }
50         captor1.apply(verifyCaptor)
51         captor2.apply(verifyCaptor)
52         captor3.apply(verifyCaptor)
53         captor4.apply(verifyCaptor)
54     }

5.4.7 OngoingStubbing

 1     @Test
 2     fun testOngoingStubbing_methodCall() {
 3         /* Given */
 4         val mock = mock<Open>()
 5         mock<Open> {
 6             on(mock.stringResult()).doReturn("A")
 7         }
 8 
 9         /* When */
10         val result = mock.stringResult()
11 
12         /* Then */
13         expect(result).toBe("A")
14     }
15 
16     @Test
17     fun testOngoingStubbing_builder() {
18         /* Given */
19         val mock = mock<Methods> { mock ->
20             on { builderMethod() } doReturn mock
21         }
22 
23         /* When */
24         val result = mock.builderMethod()
25 
26         /* Then */
27         expect(result).toBeTheSameAs(mock)
28     }
29 
30     @Test
31     fun testOngoingStubbing_nullable() {
32         /* Given */
33         val mock = mock<Methods> {
34             on { nullableStringResult() } doReturn "Test"
35         }
36 
37         /* When */
38         val result = mock.nullableStringResult()
39 
40         /* Then */
41         expect(result).toBe("Test")
42     }
43 
44     @Test
45     fun testOngoingStubbing_doThrow() {
46         /* Given */
47         val mock = mock<Methods> {
48             on { builderMethod() } doThrow IllegalArgumentException()
49         }
50 
51         try {
52             /* When */
53             mock.builderMethod()
54             fail("No exception thrown")
55         } catch (e: IllegalArgumentException) {
56         }
57     }
58 
59     @Test
60     fun testOngoingStubbing_doThrowClass() {
61         /* Given */
62         val mock = mock<Methods> {
63             on { builderMethod() } doThrow IllegalArgumentException::class
64         }
65 
66         try {
67             /* When */
68             mock.builderMethod()
69             fail("No exception thrown")
70         } catch (e: IllegalArgumentException) {
71         }
72     }
73 
74     @Test
75     fun testOngoingStubbing_doThrowVarargs() {
76         /* Given */
77         val mock = mock<Methods> {
78             on { builderMethod() }.doThrow(
79                   IllegalArgumentException(),
80                   UnsupportedOperationException()
81             )
82         }
83 
84         try {
85             /* When */
86             mock.builderMethod()
87             fail("No exception thrown")
88         } catch (e: IllegalArgumentException) {
89         }
90 
91         try {
92             /* When */
93             mock.builderMethod()
94             fail("No exception thrown")
95         } catch (e: UnsupportedOperationException) {
96         }
97     }

5.4.8 完整

  详见:https://github.com/mockito/mockito-kotlin/tree/main/tests/src/test/kotlin/test

5.5 支持打桩扩展函数吗?

 1     // 测试扩展
 2     @Test @SmallTest
 3     fun test_mock_kotlin_ext(){
 4         // given
 5         open class Student{ fun run() = 1 }
 6         fun Student.stop() = 2
 7 
 8         val mock = Mockito.spy(Student::class.java)
 9         assertTrue(mock.stop() == 2)
10 
11         // when
12         Mockito.`when`(mock.run()).thenReturn(10)
13         Mockito.`when`(mock.stop()).thenReturn(20)
14 
15         // then
16         assertTrue(mock.stop() == 20)
17 
18     }

报错如下:

when() requires an argument which has to be 'a method call on a mock'.
For example:
    when(mock.getArticles()).thenReturn(articles);

Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
   Those methods *cannot* be stubbed/verified.
   Mocking methods declared on non-public parent classes is not supported.
2. inside when() you don't call method on mock but on some other object.

6.打桩模板

6.1 系统常用类模板

 1     @Test @SmallTest
 2     fun test_mock_template1(){
 3         // given
 4         val list /*: MutableList<Any>*/ = Mockito.mock(MutableList::class.java)
 5         val hashMap = Mockito.mock(HashMap::class.java)
 6         val hashSet = Mockito.mock(HashSet::class.java)
 7 
 8 //        list.add("item0")
 9         // when
10         Mockito.`when`(list[0]).thenReturn("item0")
11         Mockito.`when`(hashMap["key"]).thenReturn("value")
12         Mockito.`when`(hashSet.size).thenReturn(20)
13 
14         // then
15         assertTrue(list[0]          == "item0")
16         assertTrue(hashMap["key"]   == "value")
17         assertTrue(hashSet.size     == 20     )
18     }

6.2 自定义的类模板并针对特定类型打桩

  如对 UUU<String> 的实例打桩。

示例1(java)

1     @Test @SmallTest
2     public void test_template(){
3         class UUU <T> { public int values(T ... ts){ return ts.length; } }
4 
5         UUU<String> mock = Mockito.mock(UUU.class);
6 
7         Mockito.when(mock.values(Mockito.any())).thenReturn(20);
8         assertTrue(mock.values("v1","v2","v3") == 20);
9     }

  这里生成了一个UUU<String>类型的模拟对象,可对它打桩。

示例2(Kotlin)

 1     @Test @SmallTest
 2     fun test_mock_template3() {
 3         open class UUU <T> { fun values(vararg  args : T?) = args.size }
 4         //val mock1 = Mockito.mock(UUU<String>::class.java)         //failed
 5         val mock2 = Mockito.mock(UUU::class.java)                   //ok,但是没有指定类型
 6         val mock3 = Mockito.mock(UUU<String>()::class.java)         //ok,正解。
 7         val spy1  = Mockito.spy(UUU<String>())                      //ok
 8 
 9         Mockito.`when`(mock3.values(any())).thenReturn(20)
10         Mockito.`when`(spy1.values(any())).thenReturn (30)
11         assertTrue(mock3.values("v1","v2","v5")  ==    20)
12         assertTrue(spy1.values ("v1","v2","v5")  ==    30)
13     }

  第4行编译不过,Kotlin 和 java 都不支持 UUU<String>::class.java  这种写法。

  第5行虽然编译通过,它生成的是UUU<*>类型,不是具体类型,无意义。

  第6行的红色()是关键,先生成个对象就可得到Class< UUU<String>> 了。

  第7行 spy函数使用不受UUU<String>::class.java  这种语法影响。

6.3 mockito-kotlin 拓展库

虽然 mockito-inline库中,有 UUU<String>::class.java  这种语法影响,但是mockito-kotlin库中没有。

 1     @Test @SmallTest
 2     fun mockito_kotlin_test_template(){
 3         val list = mock<List<String>> {
 4             `when`(it[0]).thenReturn("one")
 5         }
 6         val map = mock<Map<String,Int>> {
 7             `when`(it["key1"]).thenReturn(1)
 8         }
 9         val set = mock<MutableSet<String>> {
10             //doNothing().`when`(it).clear()
11             `when`(it.size).thenReturn(1)
12         }
13         assertTrue(list[0]      == "one")
14         assertTrue(map["key1"]  == 1    )
15         assertTrue(set.size     == 1    )
16 
17         set.clear()
18         verify(set).clear()
19     }

6.4 打桩成员函数模板

 1     @Test @SmallTest
 2     fun test_mock_template4() {
 3         open class UUU{ inline fun < reified T : Any> values(vararg  args : T?) = args.size }
 4         val mock = Mockito.mock(UUU::class.java)
 5         `when`(mock.values<String>(Mockito.any())).thenReturn(10)
 6         assertTrue(mock.values("v1","v2","v5") == 10)
 7 
 8         `when`(mock.values<Int>(Mockito.any())).thenReturn(20)
 9         assertTrue(mock.values(1,2,3) == 20)
10     }

 

posted @ 2021-12-11 22:34  f9q  阅读(1622)  评论(0编辑  收藏  举报