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)  int argument that is equal to the given 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(默认) 、WARNLENIENT  ,可以通过如下语句修改

 @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 的字段被忽略,不注入。
可打桩

它是一个真实对象,默认走真实函数,但是可打桩。

不可以嵌套依赖

构造注入

当有多个构造函数时:

  1. 默认选择参数最多的那个构造函数,如果参数与依赖对象不完全匹配(类型、个数),则构造注入失败
  2. 构造注入失败后,进入属性注入阶段或字段注入阶段。
  3. 不可打桩模拟的参数(Int、float等,interface等直接宣告构造注入阶段失败。

要么提供一个与依赖对象完全匹配构造函数,要么提供一个无参的构造函数用来初始化进而属性注入或者字段注入

属性注入(setter)
  1. 如果发现@InjectMocks的对象还没有初始化,且拥有无参构造,那么要先这个这个无参构造初始化@InjectMocks的对象
  2. 按类型匹配、名字配置规则选择相应的setter,没有则不注入。
字段注入(private Field)
  1. 如果发现@InjectMocks的对象还没有初始化,且拥有无参构造,那么要先这个这个无参构造初始化@InjectMocks的对象
  2. 按类型匹配、名字配置规则选择相应的字段,没有则不注入。

步骤:

  • @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     }

 

 

 

posted @ 2021-10-18 09:18  f9q  阅读(774)  评论(0编辑  收藏  举报