android 单元测试(3)插庄单元测试与Mockito
1.官方文档
android 文档 https://developer.android.google.cn/training/testing/unit-testing/instrumented-unit-tests?hl=zh-cn
示例 https://github.com/android/testing-samples/tree/master/unit/BasicUnitAndroidTest
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
插桩单元测试通常在设备或模拟器上运行 .插桩测试提供的保真度比本地单元测试要高,但运行速度要慢得多。建议只有在必须针对真实设备的行为进行测试时才使用插桩单元测试。如果测试依赖于自己的库,可以使用模拟框架(如 Mockito)来模拟依赖项。
2.简单示例
2.1 引入库
1 dependencies { 2 ... 3 // Optional -- mockito 4 // testImplementation "org.mockito:mockito-core:4.2.0" 5 testImplementation "org.mockito:mockito-inline:4.2.0" 6 androidTestImplementation "org.mockito:mockito-android:4.2.0" 7 }
- testImplementation : 为
test
目录内源码添加依赖 - androidTestImplementation : 为
androidTest目录内源码
添加依赖 - androidTestImplementation 要依赖 "org.mockito:mockito-android:4.2.0" 而不是 "org.mockito:mockito-core:4.2.0"
- 使用 org.mockito:mockito-inline:4.2.0 后,不用在手写MockMaker扩展文件。就可以模拟final、enum、kotlin等的对象。
在应用的模块级 build.gradle
文件中设置AndroidJUnitRunner 为默认插桩测试运行程序.
1 android { 2 defaultConfig { 3 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 4 } 5 }
2.2 示例代码
1 @Test @SmallTest 2 fun test_mock_kotlin(){ 3 // given 4 open class Student (var name : String = "null"){ fun run(){ /*...*/ } } 5 6 // mock creation 7 val student = Mockito.mock(Student::class.java) 8 9 // when 10 Mockito.`when`(student.name).thenReturn("li4") 11 Mockito.doNothing().`when`(student).run() 12 // ... other 13 student.run() 14 15 // then 16 assertTrue(student.name == "li4" ) 17 }
模拟对象创建后,成员值都是类型对应的默认值 [ 0 , false , 空集合,null ],在使用when().then()系列函数给某成员打桩后,该成员才有意义。
2.3 注意事项
- Do not mock types you don’t own
- Don’t mock value objects
- Don’t mock everything
- Show love with your tests!
- final/private/equals()/hashCode() 等函数不可以打桩
3.常用api简介
https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html
3.1 对象创建相关
3.1.1 mock()
作用 : [ 根据类型(类,接口),创建一个模拟对象。 ]
基础版本示例:
1 @Test @SmallTest 2 fun test_mock_1(){ 3 // given 4 open class Student (var name : String = "null",var age : Int = 0) 5 // mock creation 6 val stu1 = Mockito.mock(Student::class.java) 7 val stu2 = Mockito.mock(Student::class.java,"stu2") 8 9 // when 10 Mockito.`when`(stu1.name).thenReturn("NAME" ) 11 Mockito.`when`(stu2.age).thenReturn(16 ) 12 // then 13 assertTrue(stu1.name == "NAME" ) 14 assertTrue(stu2.age == 16 ) 15 }
指定所有成员函数默认行为的版本: mock(Class<T> classToMock, Answer defaultAnswer)
1 @Test @SmallTest 2 fun test_mock_answer(){ 3 // given 4 open class Student (){ fun run() = 10} 5 // mock creation 6 val stu1 = Mockito.mock(Student::class.java,CALLS_REAL_METHODS) 7 // CALLS_REAL_METHODS 8 assertTrue(stu1.run() == 10 ) 9 // when 10 Mockito.`when`(stu1.run()).thenReturn(30) 11 // then 12 assertTrue(stu1.run() == 30 ) 13 }
其中 CALLS_REAL_METHODS 就是Mockito 预先定义好的 answer,表示 返回它在类定义时返回的值 ,更多: Answer
自定义配置的版本: mock(Class<T> classToMock, MockSettings mockSettings)
1 @Test @SmallTest 2 fun test_mock_settings(){ 3 // given 4 class Student(){ fun value1() = -1 fun copy() = Student()} 5 val setting = Mockito.withSettings().name("MSettings").defaultAnswer(RETURNS_SMART_NULLS) 6 7 val stu1 = Mockito.mock(Student::class.java, setting) 8 9 // when 10 Mockito.`when`(stu1.value1()).thenReturn(100 ) 11 12 // then 13 assertTrue("stubbed",stu1.value1() == 100 ) 14 try { 15 println("stu1.copy() = ${stu1.copy().value1()}") 16 }catch (e1 : SmartNullPointerException){ 17 println("SmartNullPointerException") 18 }catch (e2 : NullPointerException){ 19 println("NullPointerException") 20 }catch (e3 : Exception){ 21 println("Exception") 22 } 23 }
- 第5行指定了一个配置(指定了名字和answer),第7行用它构造。
- 结果:copy() 会抛出 SmartNullPointerException 异常。
3.1.2 mockStatic()
用来模拟一个静态类,不是模拟一个类的静态成员。
1 open class StuStatic1(){ companion object{ 2 @JvmStatic fun value1() = -1 3 @JvmStatic fun copy() = StuStatic1() } 4 } 5 object StuStatic2{ 6 @JvmStatic fun value1() = -1 7 @JvmStatic fun copy() = StuStatic1() 8 } 9 @Test @SmallTest 10 fun test_mock_static(){ 11 // given 12 val stu2 = Mockito.mockStatic(StuStatic2::class.java, RETURNS_SMART_NULLS) 13 // when 14 Mockito.`when`(StuStatic2.value1()).thenReturn(100 ) 15 // then 16 assertTrue(StuStatic2.value1() == 100 ) //ok 17 18 // given 19 val stu1 = Mockito.mockStatic(StuStatic1::class.java, RETURNS_SMART_NULLS) 20 // when 21 Mockito.`when`(StuStatic1.value1()).thenReturn(200 ) 22 // then 23 assertTrue(StuStatic1.value1() == 200 ) //failed 24 }
3.1.3 spy() 部分模拟
- spy 的参数是真实对象,mock 的参数是 class。
- spy 生成的对象,调用其方法时默认会走真实方法。mock对象默认伪值(0,fasle,null,空集合)。
1 @Test @SmallTest 2 fun test_mock_spy(){ 3 // given 4 open class Student () { fun value1() = 1 fun value2() = 2 } 5 6 //spy 7 val stu = Student() 8 val spy = Mockito.spy(stu) 9 10 //when 11 Mockito.`when`(spy.value1()).thenReturn(100) 12 13 //then 14 assertTrue(spy.value1() == 100 ) //ok 15 assertTrue(spy.value2() == 12 ) //failed 16 }
结果:第14行通过,15行失败(因为value2()未打桩,调用的是实际版本value() = 2)。
3.1.4 reset()
重置模拟对象到初始状态,取消全部打桩:
- mock 的对象 : 函数返回 0、false、空集合、null等
- spy的 对象 : 真实函数被调用
- 它的参数是个变参:<T> void reset(T... mocks) ,调用时可传多个同类型实参。
1 @Test @SmallTest 2 fun test_mock_reset(){ 3 // given 4 open class Student{ fun value1() = 1 } 5 6 //mock、spy 7 val mock = Mockito.mock(Student::class.java) 8 val spy = Mockito.spy(Student()) 9 10 // when 11 Mockito.`when`(mock.value1()).thenReturn(100) 12 Mockito.`when`(spy.value1()).thenReturn(200 ) 13 14 // then 15 assertTrue(mock.value1() == 100 ) //ok 16 assertTrue(spy.value1() == 200 ) //ok 17 18 // reset(T... mocks) 变参,支持同时多个 19 Mockito.reset(mock,spy) 20 assertTrue(mock.value1() == 0 ) //ok 21 assertTrue(spy.value1() == 1 ) //ok 22 }
3.2 打桩相关
3.2.1 when()
用来指定要打桩的函数
1 @Test @SmallTest 2 fun test_mock_when(){ 3 // given 4 open class Student{ fun run() = 10} 5 val stu1 = Mockito.mock(Student::class.java,RETURNS_SMART_NULLS) 6 // when 7 Mockito.`when`(stu1.run()).thenReturn(30) 8 // then 9 assertTrue(stu1.run() == 30 ) 10 }
3.2.2 thenReturn()
thenReturn 用来指定特定函数的返回值,有两个重载版本:
- OngoingStubbing<T> thenReturn(T value);
- OngoingStubbing<T> thenReturn(T value, T... values); 变参版本,当仅指定1个时:表示每次都返回该值;当指定多个时:分别对应每1次调用、第2次调用、第n次,超出后使用默认。
1 @Test @SmallTest 2 fun test_mock_then(){ 3 // given 4 open class Student{ fun run() = 10} 5 val stu1 = Mockito.mock(Student::class.java) 6 // when 7 Mockito.`when`(stu1.run()).thenReturn(20,30,40) 8 // then 9 assertTrue(stu1.run() == 20 ) 10 assertTrue(stu1.run() == 30 ) 11 assertTrue(stu1.run() == 40 ) 12 assertTrue(stu1.run() == 0 ) 13 }
这个thenReturh指定了3次的返回值,第9,10,11,行调用是分别使用对应的打桩值,
第12行调用的时候,打桩值已经用完,这时用的默认值。
3.2.3 thenAnswer()
自定义打桩函数的回调用,指定该函数的返回值。语法 : when(mock.fun()).thenAnswer(answer) ,不与thenReturn一起使用。
1 @Test @SmallTest 2 fun test_mock_answer2(){ 3 // given 4 open class Student{ fun value() = 10} 5 val stu1 = Mockito.mock(Student::class.java) 6 7 // when 8 Mockito.`when`(stu1.value()).thenAnswer(object : Answer<Int>{ 9 override fun answer(invocation: InvocationOnMock?): Int { 10 val args = invocation?.arguments 11 val mock = invocation?.mock 12 //... 13 return 20 14 } 15 }) 16 // then 17 assertTrue(stu1.value() == 20 ) //ok 18 19 // when 20 Mockito.`when`(stu1.value()).thenAnswer({ invocation -> /* Lambda */ 21 val args = invocation?.arguments 22 val mock = invocation?.mock 23 30 24 }) 25 // then 26 assertTrue(stu1.value() == 30 ) //ok 27 28 // when 29 Mockito.`when`(stu1.value()).thenReturn(40).thenAnswer({ invocation -> 30 50 31 }) 32 assertTrue(stu1.value() == 50 ) //failed. value = 40 33 }
第29行,threnReturn与thenAnswer一起使用了,这时stu1.value() 返回40,且,answer未起作用。
3.2.4 thenThrow()
自定义打桩函数在调用时抛出的异常.
1 @Test @SmallTest 2 fun test_mock_throw(){ 3 // given 4 open class Student{ fun run() = 10 fun stop() = Unit} 5 val stu1 = Mockito.mock(Student::class.java) 6 7 try { 8 Mockito.`when`(stu1.run()).thenThrow(NullPointerException()) 9 stu1.run() 10 }catch (e : NullPointerException){ 11 e.printStackTrace() 12 } 13 14 try { 15 Mockito.doThrow(NullPointerException()).`when`(stu1).stop() 16 stu1.stop() 17 }catch (e : NullPointerException){ 18 e.printStackTrace() 19 } 20 }
注意第8 行和第15行的区别。
3.2.5 doNothing()系列
doReturn() doThrow() doAnswer() doNothing() doCallRealMethod()等
这些函数用来指定mock对象的成员函数为void时的行为,示例如下:
1 @Test @SmallTest //void 2 fun test_mock_void(){ 3 // given 4 open class Student{fun voidFun() {} } 5 // mock creation 6 val mock = Mockito.mock(Student::class.java) 7 8 // 测试doNothing 9 Mockito.doNothing().`when`(mock).voidFun() 10 mock.voidFun() 11 Mockito.verify(mock).voidFun() 12 13 // 测试 doThrow 14 Mockito.reset(mock) 15 Mockito.doThrow(NullPointerException("void")).`when`(mock).voidFun() 16 try { 17 mock.voidFun() 18 }catch (e : NullPointerException){ 19 e.printStackTrace() 20 }finally { 21 Mockito.verify(mock).voidFun() 22 } 23 24 // 测试 doAnswer 25 Mockito.reset(mock) 26 val answer = object : Answer<Unit>{ 27 override fun answer(invocation: InvocationOnMock?) { 28 println("void function .answer.") 29 } 30 } 31 Mockito.doAnswer(answer).`when`(mock).voidFun() 32 mock.voidFun() 33 Mockito.verify(mock, times(1)).voidFun() 34 }
注意doXX要在when前,且when内的参数是mock对象不是成员函数。
3.3 形参匹配器
3.3.1 作用
在 Mockito 中,有一系列函数:anyInt()、anyBoolean()、any(Class<T> type)、anyCollection()、eq(char value)、byteThat(ArgumentMatcher<Byte> matcher)等,它们就是形参匹配器。用来给函数的形参指定一个模糊的实参,而不是一个具体的参数。如 :
- list.get(0),精确的参数,表示list中第1个元素
- list.get(anyInt()) ,用来模糊通配一个形参,这里表示list中任意元素,即使超过最大数量,如 list.get(9999999).
- 如果通一个函数的参数经多次匹配(精确匹配或模糊匹配 ),mockito优先选择最新声明的匹配。
3.3.2 示例
1 @Test @SmallTest //形参匹配器,基本类型,类类型,变参。 2 fun test_mock_args(){ 3 // given 4 open class Student(){ 5 fun value1(x : Student,y : Int ) = y 6 fun value2(x : Student? ) {} 7 fun varargs(vararg names :String) = names.size 8 } 9 10 // mock creation 11 val mock = Mockito.mock(Student::class.java) 12 13 // when 14 // 基本类型,类类型, 15 Mockito.`when`(mock.value1(any() ?: Student(),anyInt())).thenReturn(1 ) 16 Mockito.doNothing().`when`(mock).value2(any()) 17 //Mockito.`when`(mock.value1(same(mock),anyInt())).thenReturn(1 ) 18 //Mockito.`when`(mock.value1(isA(Student::class.java),anyInt())).thenReturn(1 ) 19 20 // 变参 21 Mockito.`when`(mock.varargs(any() ?: String())).thenCallRealMethod() 22 23 // then 24 assertTrue(mock.value1(Student(),2) == 1 ) 25 assertTrue(mock.varargs("li","zhang","wang","zhao") == 4 ) 26 }
- 函数value1 有一个类类型及基本类型。函数 varargs 参数是变参。
- 这些静态函数定义在ArgumentMatchers类中,但也可以用 Mockito.anyInt() 这种方式调用。
- 如果上述代码中value1 参数是非null的,由于ArgumentMatchers.any() 返回null,运行时会出错,解决方案参考 这里 ,代码如下:
1 object MockitoHelper { 2 fun <T> anyObject(): T { 3 Mockito.any<T>() 4 return uninitialized() 5 } 6 @Suppress("UNCHECKED_CAST") 7 fun <T> uninitialized(): T = null as T 8 } 9 10 @RunWith(AndroidJUnit4::class) 11 class MockitoKotlin { 12 13 @Test @SmallTest 14 fun object_args(){ 15 // given 16 open class Student{ fun say(x : Student) {} } 17 18 // mock creation 19 val mock = Mockito.mock(Student::class.java) 20 21 //1 22 val student = Student() 23 Mockito.doNothing().`when`(mock).say(student) 24 mock.say(student) 25 Mockito.verify(mock).say(student) 26 27 //2 28 Mockito.reset(mock) 29 Mockito.doNothing().`when`(mock).say(ArgumentMatchers.any() ?: Student()) 30 mock.say(ArgumentMatchers.any() ?: Student()) 31 Mockito.verify(mock).say(ArgumentMatchers.any() ?: Student()) 32 33 //3 34 Mockito.reset(mock) 35 Mockito.doNothing().`when`(mock).say(MockitoHelper.anyObject()) 36 mock.say(MockitoHelper.anyObject()) 37 Mockito.verify(mock).say(MockitoHelper.anyObject()) 38 } 39 40 }
3.3.3 匹配器列表
详细在: https://javadoc.io/static/org.mockito/mockito-core/4.2.0/org/mockito/ArgumentMatchers.html
Method and Description | |
---|---|
any() Matches anything, including nulls and varargs. |
any(Class<T> type) |
anyInt() Any int or non-null Integer . |
anyList() |
anyIterable() Any non-null Iterable . |
anyLong() |
anyString() | anyMap() |
argThat(ArgumentMatcher<T> matcher) Allows creating custom argument matchers. |
byteThat(ArgumentMatcher<Byte> matcher) |
booleanThat(ArgumentMatcher<Boolean> matcher) Allows creating custom boolean argument matchers. |
contains(String substring) |
intThat(ArgumentMatcher<Integer> matcher) | endsWith(String suffix) |
isNotNull() Not null argument. |
isNull() |
eq(int value) |
eq(float value) |
... | ... |
3.3.4 自定义匹配器
https://javadoc.io/static/org.mockito/mockito-core/4.2.0/org/mockito/ArgumentMatcher.html
3.4 Answer
mockito库中定义了很多Answer,用来指定成员函数未打桩时的默认行为:
3.4.1 预定义的answer
参数 | 成员函数的返回模式 |
RETURNS_DEFAULTS(new GloballyConfiguredAnswer()) | 返回0、空集合、空字符串等 |
RETURNS_SMART_NULLS(new ReturnsSmartNulls()) |
智能返回模式: 先尝试返回0、空集合、空字符串等 再尝试返回SmartNull(它比null友好:有栈信息,行号等) 都失败后,返回null |
RETURNS_MOCKS(new ReturnsMocks()) |
先尝试返回(0、空集合、空字符串等),然后尝试返回mock。 如果返回类型不能被mock(例如final),则返回null |
RETURNS_DEEP_STUBS(new ReturnsDeepStubs()) |
打开链式调用,如 when(mock.getBar(anyString()).getThingy().getName()).thenReturn("deep") |
CALLS_REAL_METHODS(new CallsRealMethods()) | 未打桩的函数则返回它在类定义时返回的值。 |
RETURNS_SELF(new TriesToReturnSelf()) | 返回对象本身,用于由builder构造。 |
自定义:请参考 ReturnsSmartNulls |
3.4.2 自定义answer
1 // when 2 Mockito.`when`(mock.value()).thenAnswer(object : Answer<Int>{ 3 override fun answer(invocation: InvocationOnMock?): Int { 4 val args = invocation?.arguments 5 val mock = invocation?.mock 6 //... 7 return 20 8 } 9 })
3.5 验证相关
3.5.1 verify()
用于验证mock对象函数的执行状态:是否执行,执行次数,多个函数的执行顺序等。
1 @Test @SmallTest 2 fun test_mock_verify(){ 3 // given 4 open class Student{ 5 fun value1() = 1 6 fun say() { println("hello ~") } 7 fun run() { say() } 8 } 9 // mock 10 val mock = Mockito.mock(Student::class.java,"student") 11 // when 12 Mockito.`when`(mock.value1()).thenReturn(10 ) 13 14 //Mockito.verify(mock).value1() // failed 15 16 //println("mock.value = ${mock.value1()}") // ok 17 //Mockito.verify(mock).value1() 18 19 assertTrue(mock.value1() == 10) 20 Mockito.verify(mock).value1() // ok 21 22 mock.run() 23 Mockito.verify(mock).say() // failed 24 }
执行到第14行时,还未调用value1()函数,验证不通过。
第19行调用了value1(),第20行验证通过。
第22行通过run调用了say(),但是第23行验证不通过。
3.5.2 times()、atLeast()、atLeastOnce()、never()
验证打桩函数的执行次数。
1 @Test @SmallTest 2 fun test_mock_times(){ 3 // given 4 open class Student{fun value1() = 1 fun value2() = 2 fun value3() = 3} 5 // mock 6 val mock = Mockito.mock(Student::class.java,"student") 7 // when 8 Mockito.`when`(mock.value1()).thenReturn(10 ) 9 10 println("mock.value = ${mock.value1()}") 11 assertTrue(mock.value1() == 10) 12 Mockito.verify(mock, Mockito.times(2)).value1() //ok 13 Mockito.verify(mock, Mockito.atLeast(1)).value1() //ok 14 15 mock.value2() 16 Mockito.verify(mock, Mockito.atLeastOnce()).value2()//ok 17 Mockito.verify(mock, Mockito.never()).value3() //ok 18 Mockito.verify(mock, Mockito.times(0)).value3() //ok 19 }
3.5.3 inOrder()
验证函数的调用先后顺序是否符合预期,不是必须按序号排列: 1、6、100就通过。它支持同时关联多个模拟对象。
1 @Test @SmallTest 2 fun test_mock_inorder(){ 3 // given 4 open class Student1{fun value1() = 1 fun value2() = 2 fun value3() = 3} 5 // mock 6 val mock1 = Mockito.mock(Student1::class.java,"student1") 7 8 val order = Mockito.inOrder(mock1) 9 mock1.value1() 10 mock1.value2() 11 12 //order.verify(mock1).value2() //ok 13 //order.verify(mock1).value1() //failed 14 15 //order.verify(mock1).value3() //failed 16 17 order.verify(mock1).value1() //ok 18 order.verify(mock1).value2() //ok 19 20 21 open class Student2{fun value4() = 4 fun value5() = 5 fun value6() = 6} 22 val mock2 = Mockito.mock(Student2::class.java,"student2") 23 val order2 = Mockito.inOrder(mock1,mock2) 24 25 mock2.value6() 26 mock2.value5() 27 28 order2.verify(mock1).value2() //ok 29 order2.verify(mock2).value6() //ok 30 order2.verify(mock2).value4() //failed ,value4 called after verify 31 32 mock2.value4() 33 }
- 第13行:value1是在value2之前调用的
- 第15行:value3从未调用过
- 第30行:虽然value4的调用在value6之后,但是也在verify之后。
3.5.4 timeout、after
timeout() : 用来设置verify()的超时时间(单位ms),超时后验证失败。不可与inOther一起使用。
1 @Test @SmallTest 2 fun test_timeout(){ 3 open class Student{ fun say() { println("test_timeout : say ()") } } 4 val mock = mock(Student::class.java) 5 thread { 6 sleep(1000 * 1) 7 mock.say() 8 } 9 verify(mock, timeout(1000 * 2 ).times(1)).say() 10 println("1000 * 2 ms 内成功则立即执行这句,失败不执行") 11 }
after() : 在等待多少ms后才执行verify()验证。
1 @Test @SmallTest 2 fun test_after(){ 3 open class Student{ fun say() { println("test_timeout : say ()") } } 4 val mock = mock(Student::class.java) 5 thread { 6 sleep(100 * 1) 7 mock.say() 8 } 9 verify(mock, after(1000 * 3 ).times(1)).say() 10 println("1000 * 3 ms 后才执行这句") 11 }
3.5.5 实参捕获器 ArgumentCaptor
https://javadoc.io/static/org.mockito/mockito-core/4.2.0/org/mockito/ArgumentCaptor.html
作用:
在某些场景中,不光要对方法的返回值和调用进行验证,还需要验证一系列交互后所传入方法的参数。可以用实参捕获器来捕获传入方法的参数,然后对其进行验证。
api:
- ArgumentCaptor.forClass() 构造captor.
- capture() 捕获参数
- getValue() 返回参数
- getAllValues 返回全部参数值。
示例:
say()的参数是可为null的,run()的参数是非null的。play()参数是变参。
1 @Test @SmallTest 2 fun test_mock_captor(){ 3 4 // given 5 open class Student(var name : String = ""){ 6 fun say(word : String?) { } 7 fun run(x : Student) { } 8 fun play(vararg student: Student? ) = student.size 9 } 10 val mock = Mockito.mock(Student::class.java) 11 12 // 可空参数 13 val captor = ArgumentCaptor.forClass(String::class.java) 14 Mockito.doNothing().`when`(mock).say(anyString()) 15 mock.say("hello") ; mock.say("world") 16 Mockito.verify(mock, Mockito.atLeastOnce()).say(captor.capture()) 17 assertTrue(captor.value == "world") 18 19 // 非空参数类对象 20 val captor2 = ArgumentCaptor.forClass(Student::class.java) 21 Mockito.doNothing().`when`(mock).run(any(Student::class.java) ?: Student("")) 22 mock.run(Student("li")) 23 Mockito.verify(mock).run(captor2.capture() ?: Student("")) 24 assertTrue(captor2.value.name == "li") 25 26 // 变参 27 val captor3 = ArgumentCaptor.forClass(Student::class.java) 28 Mockito.`when`(mock.play(any(Student::class.java) ?: Student(""))).thenCallRealMethod() 29 mock.play(Student("wang"),Student("zhao"),Student("zhang")) 30 Mockito.verify(mock).play(captor3.capture()) 31 32 assertTrue(captor3.allValues.size == 3) 33 assertTrue(captor3.allValues[1].name == "zhao") 34 35 }
3.5.5 lenient()
官方文档:
https://javadoc.io/static/org.mockito/mockito-core/4.2.0/org/mockito/Mockito.html#lenient--
Mockito对存根或者模拟对象有3种检查模式 : STRICT_STUBS(默认) 、WARN、LENIENT ,可以通过如下语句修改
@Rule val mockito : MockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS)
当一个存根被修饰成lenient(),那么该存根检查为宽松模式,它的潜在问题被忽略,如:多余的存根UnnecessaryStubbingException、参数不匹配 等等.
忽略某个存根用:Mockito.lenient() ,
忽略mock对象用:MockSettings.lenient()
示例如下:
1 //@Rule val mockito : MockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS) 2 @Test @SmallTest 3 fun test_mock_lenient(){ 4 5 // given 6 open class Student{fun value1() = 1 fun value2() = 2 fun value3() = 3} 7 8 // mock 9 val mock = Mockito.mock(Student::class.java) 10 // when 11 Mockito.lenient().`when`(mock.value1()).thenReturn(10) 12 // then 13 assertTrue(mock.value1() == 10) 14 assertTrue(mock.value2() == 0 ) 15 16 // mock 17 val mock2 = Mockito.mock(Student::class.java, Mockito.withSettings().lenient()) 18 // when 19 Mockito.`when`(mock2.value1()).thenReturn(10) 20 // then 21 assertTrue(mock2.value1() == 10) 22 assertTrue(mock2.value3() == 3 ) 23 }
4.常用注解
常用注解一般都用 MockitoJUnitRunner。
4.1 @Mock
作用:
用注解的方式简化代码,生成模拟对象。不用Mockito.mock(xxx.class)
步骤:
- 设置测试类的JUnitRunner 改为 MockitoJUnitRunner
- 使用@Mock 注解成员变量
- 在@Test函数之前运行openMocks初始化注解,一般放在@Before内,在init内openMock无效、在@Test函数第1句也无效
- 使用测试语句when..then...verify/assert等。
示例:
1 @RunWith(MockitoJUnitRunner::class) 2 class KotlinMockAnnotations{ 3 4 open class MockTest{fun value1() = 1 fun value2() = 2 fun value3() = 3} 5 6 @Mock lateinit var mock : MockTest 7 @Before fun setup() { MockitoAnnotations.openMocks(this) } 8 // init { MockitoAnnotations.openMocks(this) }/*init内openMock无效*/ 9 10 @Test @SmallTest 11 fun test_mock_annotations(){ 12 //MockitoAnnotations.openMocks(this) //在@Test函数第1句也无效 13 14 // when 15 Mockito.`when`(mock.value1()).thenReturn(10) 16 17 // then 18 assertTrue(mock.value1() == 10) //ok 19 assertTrue(mock.value3() == 0 ) //ok 20 assertTrue(mock.value2() == 2 ) //failed value2 = 0 21 } 22 /*@Test 23 fun test_mock_annotations2(@Mock mock2 : MockTest){ //这个@Mock无法运行 24 // when 25 Mockito.`when`(mock2.value1()).thenReturn(10) 26 27 // then 28 assertTrue(mock2.value1() == 10) //ok 29 }*/ 30 }
4.2 @Captor
作用:
简化生成ArgumentCaptor ,关于ArgumentCaptor 详见:这里
示例:
1 @RunWith(MockitoJUnitRunner::class) 2 class KotlinCaptorAnnotations{ 3 4 open class MockTest{fun value1(name : String?) {} } 5 6 @Mock lateinit var mock : MockTest 7 @Captor lateinit var captor : ArgumentCaptor<String> 8 @Before fun setup(){ MockitoAnnotations.openMocks(this) } 9 10 @Test @SmallTest 11 fun test_mock_captor(){ 12 // when 13 Mockito.doNothing().`when`(mock).value1(anyString()) 14 15 mock.value1("hello") 16 // then 17 verify(mock).value1(captor.capture()) 18 assertTrue(captor.value == "hello") 19 } 20 }
4.3 @Spy
作用:
简化Mockito.spy(object)相当代码 。
示例:
1 @RunWith(MockitoJUnitRunner::class) 2 class KotlinSpyAnnotations{ 3 4 open class MockTest{fun value1() = 1 fun value2() = 2 } 5 6 @Spy var mock = MockTest() 7 @Before fun setup(){ MockitoAnnotations.openMocks(this) } 8 9 @Test @SmallTest 10 fun test_mock_spy(){ 11 // when 12 Mockito.`when`(mock.value2()).thenReturn(20) 13 14 // then 15 assertTrue(mock.value1() == 1) 16 assertTrue(mock.value2() == 20) 17 } 18 }
4.4 @InjectMocks
作用:
在现实代码中,一个类往往依赖其它类,如果没有它依赖的对象很难准确完成测试。这时可用@InjectMocks完成依赖的注入(Mockito库完成)。
他的规则如下表,详见:https://javadoc.io/static/org.mockito/mockito-core/4.2.0/org/mockito/InjectMocks.html
注入方式 |
只通过构造函数、属性、setter(即使他们都是私有的)这3种方式注入,如果3种都不行,没有错误提示,这时要自行处理。 |
注入原理 |
反射 |
不支持的类型 | 本地类、抽象类、内部类、接口、嵌套的静态类 |
不支持的字段 | final 或 static 的字段被忽略,不注入。 |
可打桩 |
它是一个真实对象,默认走真实函数,但是可打桩。 不可以嵌套依赖 |
构造注入 |
当有多个构造函数时:
要么只提供一个与依赖对象完全匹配构造函数,要么提供一个无参的构造函数用来初始化进而属性注入或者字段注入。 |
属性注入(setter) |
|
字段注入(private Field) |
|
步骤:
- @RunWith(MockitoJUnitRunner::class)
- @InjectMocks 声明该成员依赖其它对象
- 用@Mock、@Spy 声明该成员被依赖
- 声明相应的构造、setter。
示例:
1 @RunWith(MockitoJUnitRunner::class) 2 class KotlinInjectMockAnnotations{ 3 4 open class Wheel {fun size()= 18} 5 open class Driver{fun age() = 1 } 6 7 open class Car{ 8 private lateinit var wheel : Wheel 9 private lateinit var driver : Driver 10 private var door : Int = 4 11 private val wheel2 = Wheel() //final 或 static 字段被忽略 12 companion object{ @JvmStatic val driver2 = Driver()} //final 或 static 字段被忽略 13 14 private constructor(i : Int , j : Int , k : Int, l : Int){ println("constructor(4)") } 15 private constructor(w : Wheel, d : Driver, r : Int) { println("constructor(3)") ;driver = d;wheel = w;} 16 private constructor(w : Wheel, d : Driver) { println("constructor(2)") ;driver = d;wheel = w;} 17 private constructor() { println("constructor(0)") } 18 19 fun wheelSize() = wheel.size() 20 fun driverAge() = driver.age() 21 fun doorSize() = door 22 } 23 24 @Spy var wheel = Wheel() 25 @Mock lateinit var driver : Driver 26 @InjectMocks lateinit var car : Car 27 @InjectMocks lateinit var wheel2 : Wheel 28 //@Mock(name = "driver") lateinit var md : Driver //@Mock可以使用name 29 30 @Before fun setup(){ MockitoAnnotations.openMocks(wheel)} 31 32 @Test @SmallTest 33 fun test_inject_mock_1(){ 34 assertTrue(driver.age() == 0 ) 35 assertTrue(wheel.size() == 18) 36 assertTrue(car.driverAge()== 0 ) 37 38 // when 39 Mockito.`when`(driver.age() ).thenReturn(26) 40 Mockito.`when`(car.driverAge() ).thenReturn(25) 41 // then 42 assertTrue(car.doorSize() == 4 ) 43 assertTrue(car.wheelSize()== 18) 44 assertTrue(car.driverAge()== 25) 45 } 46 }
其中:
- 第14、15、16、17行定义了4个构造函数、且都为私私有。
- Car类对外部有依赖,第8、9行声明了两个依赖对象 wheel与driver
- Mockito库优先选择参数最多的构造(第14行),但是它的参数与依赖项不完全匹配。构造注入失败。
- 属性注入。先用第17行的无参数构造初始化,再注入。
- 第11行字段是final无法注入
- 第12行静态成员无法注入。
4.5 @DoNotMock
声明一个类型,不可以被Mockito库 模拟、打桩、测试。如要测试它,请使用其它工具或者方法。
1 @Test @SmallTest 2 fun test_do_not_mock(){ 3 @DoNotMock open class Student{ fun value() = 1} 4 val mock = Mockito.mock(Student::class.java) 5 Mockito.`when`(mock.value()).thenReturn(10) 6 assertTrue(10 == mock.value()) 7 }