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 }