一、前期准备~
1、准备工作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<! - - mockito依赖 - - >a
<dependency>
<groupId>org.mockito< / groupId>
<artifactId>mockito - core< / artifactId>
<version> 2.7 . 19 < / version>
<scope>test< / scope>
< / dependency>
<! - - junit依赖 - - >
<dependency>
<groupId>junit< / groupId>
<artifactId>junit< / artifactId>
<version> 4.12 < / version>
<scope>test< / scope>
< / dependency>
|
2、入门知识
1
2
3
4
5
|
1 )Mockito:简单轻量级的做mocking测试的框架;
2 )mock对象:在调试期间用来作为真实对象的替代品;
3 )mock测试:在测试过程中,对那些不容易构建的对象用一个虚拟对象来代替测试的方法就叫mock测试;
4 )stub:打桩,就是为mock对象的方法指定返回值(可抛出异常);
5 )verify:行为验证,验证指定方法调用情况(是否被调用,调用次数等);
|
3、五分钟入门Demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
@Test
public void test0() {
/ / 1 、创建mock对象(模拟依赖的对象)
final List mock = Mockito.mock( List . class );
/ / 2 、使用mock对象(mock对象会对接口或类的方法给出默认实现)
System.out.println( "mock.add result => " + mock.add( "first" )); / / false
System.out.println( "mock.size result => " + mock.size()); / / 0
/ / 3 、打桩操作(状态测试:设置该对象指定方法被调用时的返回值)
Mockito.when(mock.get( 0 )).thenReturn( "second" );
Mockito.doReturn( 66 ).when(mock).size();
/ / 3 、使用mock对象的stub(测试打桩结果)
System.out.println( "mock.get result => " + mock.get( 0 )); / / second
System.out.println( "mock.size result => " + mock.size()); / / 66
/ / 4 、验证交互 verification(行为测试:验证方法调用情况)
Mockito.verify(mock).get(Mockito.anyInt());
Mockito.verify(mock, Mockito.times( 2 )).size();
/ / 5 、验证返回的结果(这是JUnit的功能)
assertEquals( "second" , mock.get( 0 ));
assertEquals( 66 , mock.size());
}
|
二、让我们开始学习吧!
1、行为验证
• 一旦mock对象被创建了,mock对象会记住所有的交互,然后你就可以选择性的验证你感兴趣的交互,验证不通过则抛出异常。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@Test
public void test1() {
final List mockList = Mockito.mock( List . class );
mockList.add( "mock1" );
mockList.get( 0 );
mockList.size();
mockList.clear();
/ / 验证方法被使用(默认 1 次)
Mockito.verify(mockList).add( "mock1" );
/ / 验证方法被使用 1 次
Mockito.verify(mockList, Mockito.times( 1 )).get( 0 );
/ / 验证方法至少被使用 1 次
Mockito.verify(mockList, Mockito.atLeast( 1 )).size();
/ / 验证方法没有被使用
Mockito.verify(mockList, Mockito.never()).contains( "mock2" );
/ / 验证方法至多被使用 5 次
Mockito.verify(mockList, Mockito.atMost( 5 )).clear();
/ / 指定方法调用超时时间
Mockito.verify(mockList, timeout( 100 )).get( 0 );
/ / 指定时间内需要完成的次数
Mockito.verify(mockList, timeout( 200 ).atLeastOnce()).size();
}
|
2、如何做一些测试桩stub
• 默认情况下,所有的函数都有返回值。mock函数默认返回的是null,一个空的集合或者一个被对象类型包装的内置类型,例如0、false对应的对象类型为Integer、Boolean;
• 一旦测试桩函数被调用,该函数将会一致返回固定的值;
• 对于 static 和 final 方法, Mockito 无法对其 when(…).thenReturn(…) 操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
@Test
public void test2() {
/ / 静态导入,减少代码量: import static org.mockito.Mockito. * ;
final ArrayList mockList = mock(ArrayList. class );
/ / 设置方法调用返回值
when(mockList.add( "test2" )).thenReturn(true);
doReturn(true).when(mockList).add( "test2" );
System.out.println(mockList.add( "test2" )); / / true
/ / 设置方法调用抛出异常
when(mockList.get( 0 )).thenThrow(new RuntimeException());
doThrow(new RuntimeException()).when(mockList).get( 0 );
System.out.println(mockList.get( 0 )); / / throw RuntimeException
/ / 无返回方法打桩
doNothing().when(mockList).clear();
/ / 为回调做测试桩(对方法返回进行拦截处理)
final Answer<String> answer = new Answer<String>() {
@Override
public String answer(InvocationOnMock invocationOnMock) throws Throwable {
final List mock = ( List ) invocationOnMock.getMock();
return "mock.size result => " + mock.size();
}
};
when(mockList.get( 1 )).thenAnswer(answer);
doAnswer(answer).when(mockList).get( 1 );
System.out.println(mockList.get( 1 )); / / mock.size result = > 0
/ / 对同一方法多次打桩,以最后一次为准
when(mockList.get( 2 )).thenReturn( "test2_1" );
when(mockList.get( 2 )).thenReturn( "test2_2" );
System.out.println(mockList.get( 2 )); / / test2_2
System.out.println(mockList.get( 2 )); / / test2_2
/ / 设置多次调用同类型结果
when(mockList.get( 3 )).thenReturn( "test2_1" , "test2_2" );
when(mockList.get( 3 )).thenReturn( "test2_1" ).thenReturn( "test2_2" );
System.out.println(mockList.get( 3 )); / / test2_1
System.out.println(mockList.get( 3 )); / / test2_2
/ / 为连续调用做测试桩(为同一个函数调用的不同的返回值或异常做测试桩)
when(mockList.get( 4 )).thenReturn( "test2" ).thenThrow(new RuntimeException());
doReturn( "test2" ).doThrow(new RuntimeException()).when(mockList).get( 4 );
System.out.println(mockList.get( 4 )); / / test2
System.out.println(mockList.get( 4 )); / / throw RuntimeException
/ / 无打桩方法,返回默认值
System.out.println(mockList.get( 99 )); / / null
}
|
3、参数匹配器
• 参数匹配器使验证和测试桩变得更灵活;
• 为了合理的使用复杂的参数匹配,使用equals()与anyX() 的匹配器会使得测试代码更简洁、简单。有时,会迫使你重构代码以使用equals()匹配或者实现equals()函数来帮助你进行测试;
• 如果你使用参数匹配器,所有参数都必须由匹配器提供;
• 支持自定义参数匹配器;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
@Test
public void test3() {
final Map mockMap = mock( Map . class );
/ / 正常打桩测试
when(mockMap.get( "key" )).thenReturn( "value1" );
System.out.println(mockMap.get( "key" )); / / value1
/ / 为灵活起见,可使用参数匹配器
when(mockMap.get(anyString())).thenReturn( "value2" );
System.out.println(mockMap.get(anyString())); / / value2
System.out.println(mockMap.get( "test_key" )); / / value2
System.out.println(mockMap.get( 0 )); / / null
/ / 多个入参时,要么都使用参数匹配器,要么都不使用,否则会异常
when(mockMap.put(anyString(), anyInt())).thenReturn( "value3" );
System.out.println(mockMap.put( "key3" , 3 )); / / value3
System.out.println(mockMap.put(anyString(), anyInt())); / / value3
System.out.println(mockMap.put( "key3" , anyInt())); / / 异常
/ / 行为验证时,也支持使用参数匹配器
verify(mockMap, atLeastOnce()).get(anyString());
verify(mockMap).put(anyString(), eq( 3 ));
/ / 自定义参数匹配器
final ArgumentMatcher<ArgumentTestRequest> myArgumentMatcher = new ArgumentMatcher<ArgumentTestRequest>() {
@Override
public boolean matches(ArgumentTestRequest request) {
return "name" .equals(request.getName()) || "value" .equals(request.getValue());
}
};
/ / 自定义参数匹配器使用
final ArgumentTestService mock = mock(ArgumentTestService. class );
when(mock.argumentTestMethod(argThat(myArgumentMatcher))).thenReturn( "success" );
doReturn( "success" ).when(mock).argumentTestMethod(argThat(myArgumentMatcher));
System.out.println(mock.argumentTestMethod(new ArgumentTestRequest( "name" , "value" ))); / / success
System.out.println(mock.argumentTestMethod(new ArgumentTestRequest())); / / null
}
|
4、执行顺序验证
• 验证执行顺序是非常灵活的-你不需要一个一个的验证所有交互,只需要验证你感兴趣的对象即可;
• 你可以仅通过那些需要验证顺序的mock对象来创建InOrder对象;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
@Test
public void test4() {
/ / 验证同一个对象多个方法的执行顺序
final List mockList = mock( List . class );
mockList.add( "first" );
mockList.add( "second" );
final InOrder inOrder = inOrder(mockList);
inOrder.verify(mockList).add( "first" );
inOrder.verify(mockList).add( "second" );
/ / 验证多个对象多个方法的执行顺序
final List mockList1 = mock( List . class );
final List mockList2 = mock( List . class );
mockList1.get( 0 );
mockList1.get( 1 );
mockList2.get( 0 );
mockList1.get( 2 );
mockList2.get( 1 );
final InOrder inOrder1 = inOrder(mockList1, mockList2);
inOrder1.verify(mockList1).get( 0 );
inOrder1.verify(mockList1).get( 2 );
inOrder1.verify(mockList2).get( 1 );
}
|
5、确保交互(interaction)操作不会执行在mock对象上
• 一些用户可能会在频繁地使用verifyNoMoreInteractions(),甚至在每个测试函数中都用。但是verifyNoMoreInteractions()并不建议在每个测试函数中都使用;
• verifyNoMoreInteractions()在交互测试套件中只是一个便利的验证,它的作用是当你需要验证是否存在冗余调用时;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
@Test
public void test5() {
/ / 验证某个交互是否从未被执行
final List mock = mock( List . class );
mock.add( "first" );
verify(mock, never()).add( "test5" ); / / 通过
verify(mock, never()).add( "first" ); / / 异常
/ / 验证mock对象没有交互过
final List mock1 = mock( List . class );
final List mock2 = mock( List . class );
verifyZeroInteractions(mock1); / / 通过
verifyNoMoreInteractions(mock1, mock2); / / 通过
verifyZeroInteractions(mock, mock2); / / 异常
/ / 注意:可能只想验证前面的逻辑,但是加上最后一行,会导致出现异常。建议使用方法层面的验证,如:never();
/ / 在验证是否有冗余调用的时候,可使用此种方式。如下:
final List mockList = mock( List . class );
mockList.add( "one" );
mockList.add( "two" );
verify(mockList).add( "one" ); / / 通过
verify(mockList, never()).get( 0 ); / / 通过
verifyZeroInteractions(mockList); / / 异常
}
|
6、使用注解简化mock对象创建
注意!下面这句代码需要在运行测试函数之前被调用,一般放到测试类的基类或者test runner中:
1
|
MockitoAnnotations.initMocks(this);
|
也可以使用内置的runner: MockitoJUnitRunner 或者一个rule : MockitoRule;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/ / 代替 mock(ArgumentTestService. class ) 创建mock对象;
@Mock
private ArgumentTestService argumentTestService;
/ / 若改注解修饰的对象有成员变量,@Mock定义的mock对象会被自动注入;
@InjectMocks
private MockitoAnnotationServiceImpl mockitoAnnotationService;
@Test
public void test6() {
/ / 注意!下面这句代码需要在运行测试函数之前被调用,一般放到测试类的基类或者test runner中;
MockitoAnnotations.initMocks(this);
when(argumentTestService.argumentTestMethod(new ArgumentTestRequest())).thenReturn( "success" );
System.out.println(argumentTestService.argumentTestMethod(new ArgumentTestRequest())); / / success
System.out.println(mockitoAnnotationService.mockitoAnnotationTestMethod()); / / null
}
|
7、监控真实对象(部分mock)
• 可以为真实对象创建一个监控(spy)对象。当你使用这个spy对象时真实的对象也会也调用,除非它的函数被stub了;
• 尽量少使用spy对象,使用时也需要小心形式,例如spy对象可以用来处理遗留代码;
• stub语法中同样提供了部分mock的方法,可以调用真实的方法;
完全mock:
上文讲的内容是完全mock,即创建的mock对象与真实对象无关,mock对象的方法默认都是基本的实现,返回基本类型。可基于接口、实现类创建mock对象。
部分mock:
所谓部分mock,即创建的mock对象时基于真实对象的,mock对象的方法都是默认使用真实对象的方法,除非stub之后,才会以stub为准。基于实现类创建mock对象,否则在没有stub的情况下,调用真实方法时,会出现异常。
注意点:
Mockito并不会为真实对象代理函数调用,实际上它会拷贝真实对象。因此如果你保留了真实对象并且与之交互,不要期望从监控对象得到正确的结果。 当你在监控对象上调用一个没有被stub的函数时并不会调用真实对象的对应函数,你不会在真实对象上看到任何效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
@Test
public void test7() {
/ / stub部分mock(stub中使用真实调用)。注意:需要mock实现类,否则会有异常
final StubTestService stubTestService = mock(StubTestServiceImpl. class );
when(stubTestService.stubTestMethodA( "paramA" )).thenCallRealMethod();
doCallRealMethod().when(stubTestService).stubTestMethodB();
System.out.println(stubTestService.stubTestMethodA( "paramA" )); / / stubTestMethodA is called, param = paramA
System.out.println(stubTestService.stubTestMethodB()); / / stubTestMethodB is called
System.out.println(stubTestService.stubTestMethodC()); / / null
/ / spy部分mock
final LinkedList<String> linkedList = new LinkedList();
final LinkedList spy = spy(linkedList);
spy.add( "one" );
spy.add( "two" );
doReturn( 100 ).when(spy).size();
when(spy.get( 0 )).thenReturn( "one_test" );
System.out.println(spy.size()); / / 100
System.out.println(spy.get( 0 )); / / one_test
System.out.println(spy.get( 1 )); / / two
/ / spy可以类比AOP。在spy中,由于默认是调用真实方法,所以第二种写法不等价于第一种写法,不推荐这种写法。
doReturn( "two_test" ).when(spy).get( 2 );
when(spy.get( 2 )).thenReturn( "two_test" ); / / 异常 java.lang.IndexOutOfBoundsException: Index: 2 , Size: 2
System.out.println(spy.get( 2 )); / / two_test
/ / spy对象只是真实对象的复制,真实对象的改变不会影响spy对象
final List <String> arrayList = new ArrayList<>();
final List <String> spy1 = spy(arrayList);
spy1.add( 0 , "one" );
System.out.println(spy1.get( 0 )); / / one
arrayList.add( 0 , "list1" );
System.out.println(arrayList.get( 0 )); / / list1
System.out.println(spy1.get( 0 )); / / one
/ / 若对某个方法stub之后,又想调用真实的方法,可以使用reset(spy)
final ArrayList<String> arrayList1 = new ArrayList<>();
final ArrayList<String> spy2 = spy(arrayList1);
doReturn( 100 ).when(spy2).size();
System.out.println(spy2.size()); / / 100
reset(spy2);
System.out.println(spy2.size()); / / 0
}
|
@mock 和 @spy的使用" class="anchor" href="#8、@mock 和 @spy的使用">8、@Mock 和 @Spy的使用
• @Mock 等价于 Mockito.mock(Object.class);
• @Spy 等价于 Mockito.spy(obj);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
区分是mock对象还是spy对象:
Mockito.mockingDetails(someObject).isMock();
Mockito.mockingDetails(someObject).isSpy();
@Mock
private StubTestService stubTestService;
@Spy
private StubTestServiceImpl stubTestServiceImpl;
@Spy
private StubTestService stubTestServiceImpl1 = new StubTestServiceImpl();
@Test
public void test8() {
MockitoAnnotations.initMocks(this);
/ / mock对象返回默认
System.out.println(stubTestService.stubTestMethodB()); / / null
/ / spy对象调用真实方法
System.out.println(stubTestServiceImpl.stubTestMethodC()); / / stubTestMethodC is called
System.out.println(stubTestServiceImpl1.stubTestMethodA( "spy" )); / / stubTestMethodA is called, param = spy
/ / 区分是mock对象还是spy对象
System.out.println(mockingDetails(stubTestService).isMock()); / / true
System.out.println(mockingDetails(stubTestService).isSpy()); / / false
System.out.println(mockingDetails(stubTestServiceImpl).isSpy()); / / true
}
|
9、ArgumentCaptor(参数捕获器)捕获方法参数进行验证。(可代替参数匹配器使用)
• 在某些场景中,不光要对方法的返回值和调用进行验证,同时需要验证一系列交互后所传入方法的参数。那么我们可以用参数捕获器来捕获传入方法的参数进行验证,看它是否符合我们的要求。
ArgumentCaptor介绍
通过ArgumentCaptor对象的forClass(Class
ArgumentCaptor的Api
argument.capture() 捕获方法参数
argument.getValue() 获取方法参数值,如果方法进行了多次调用,它将返回最后一个参数值
argument.getAllValues() 方法进行多次调用后,返回多个参数值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@Test
public void test9() {
List mock = mock( List . class );
List mock1 = mock( List . class );
mock.add( "John" );
mock1.add( "Brian" );
mock1.add( "Jim" );
/ / 获取方法参数
ArgumentCaptor argument = ArgumentCaptor.forClass(String. class );
verify(mock).add(argument.capture());
System.out.println(argument.getValue()); / / John
/ / 多次调用获取最后一次
ArgumentCaptor argument1 = ArgumentCaptor.forClass(String. class );
verify(mock1, times( 2 )).add(argument1.capture());
System.out.println(argument1.getValue()); / / Jim
/ / 获取所有调用参数
System.out.println(argument1.getAllValues()); / / [Brian, Jim]
}
|
10、简化 ArgumentCaptor 的创建
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@Mock
private List <String> captorList;
@Captor
private ArgumentCaptor<String> argumentCaptor;
@Test
public void test10() {
MockitoAnnotations.initMocks(this);
captorList.add( "cap1" );
captorList.add( "cap2" );
System.out.println(captorList.size());
verify(captorList, atLeastOnce()).add(argumentCaptor.capture());
System.out.println(argumentCaptor.getAllValues());
}
|
11、高级特性:自定义验证失败信息
1
2
3
4
5
6
7
8
9
10
11
12
|
@Test
public void test11() {
final ArrayList arrayList = mock(ArrayList. class );
arrayList.add( "one" );
arrayList.add( "two" );
verify(arrayList, description( "size()没有调用" )).size();
/ / org.mockito.exceptions.base.MockitoAssertionError: size()没有调用
verify(arrayList, timeout( 200 ).times( 3 ).description( "验证失败" )).add(anyString());
/ / org.mockito.exceptions.base.MockitoAssertionError: 验证失败
}
|
12、高级特性:修改没有测试桩的调用的默认返回值
• 可以指定策略来创建mock对象的返回值。这是一个高级特性,通常来说,你不需要写这样的测试;
• 它对于遗留系统来说是很有用处的。当你不需要为函数调用打桩时你可以指定一个默认的answer;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
@Test
public void test12(){
/ / 创建mock对象、使用默认返回
final ArrayList mockList = mock(ArrayList. class );
System.out.println(mockList.get( 0 )); / / null
/ / 这个实现首先尝试全局配置,如果没有全局配置就会使用默认的回答,它返回 0 ,空集合,null,等等。
/ / 参考返回配置:ReturnsEmptyValues
mock(ArrayList. class , Answers.RETURNS_DEFAULTS);
/ / ReturnsSmartNulls首先尝试返回普通值( 0 ,空集合,空字符串,等等)然后它试图返回SmartNull。
/ / 如果最终返回对象,那么会简单返回null。一般用在处理遗留代码。
/ / 参考返回配置:ReturnsMoreEmptyValues
mock(ArrayList. class , Answers.RETURNS_SMART_NULLS);
/ / 未stub的方法,会调用真实方法。
/ / 注 1 :存根部分模拟使用时(mock.getSomething ()) .thenReturn (fakeValue)语法将调用的方法。对于部分模拟推荐使用doReturn语法。
/ / 注 2 :如果模拟是序列化反序列化,那么这个Answer将无法理解泛型的元数据。
mock(ArrayList. class , Answers.CALLS_REAL_METHODS);
/ / 深度stub,用于嵌套对象的mock。参考:https: / / www.cnblogs.com / Ming8006 / p / 6297333.html
mock(ArrayList. class , Answers.RETURNS_DEEP_STUBS);
/ / ReturnsMocks首先尝试返回普通值( 0 ,空集合,空字符串,等等)然后它试图返回mock。
/ / 如果返回类型不能mocked(例如是final)然后返回null。
mock(ArrayList. class , Answers.RETURNS_MOCKS);
/ / mock对象的方法调用后,可以返回自己(类似builder模式)
mock(ArrayList. class , Answers.RETURNS_SELF);
/ / 自定义返回
final Answer<String> answer = new Answer<String>() {
@Override
public String answer(InvocationOnMock invocation) throws Throwable {
return "test_answer" ;
}
};
final ArrayList mockList1 = mock(ArrayList. class , answer);
System.out.println(mockList1.get( 0 )); / / test_answer
}
|
三、学习了这么多,牛刀小试一下!
测试实体类
1
2
3
4
5
6
|
@Data
public class User {
/ * *
* 姓名,登录密码
* /
|
持久层DAO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public interface UserDao {
/ * *
* 根据name查找user
* @param name
* @ return
* /
User getUserByName(String name);
/ * *
* 保存user
* @param user
* @ return
* /
Integer saveUser(User user);
}
|
业务层Service接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public interface UserService {
/ * *
* 根据name查找user
* @param name
* @ return
* /
User getUserByName(String name);
/ * *
* 保存user
* @param user
* @ return
* /
Integer saveUser(User user);
}
|
业务层Serive实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
@Service
public class UserServiceImpl implements UserService {
/ / userDao
@Autowired
private UserDao userDao;
/ * *
* 根据name查找user
* @param name
* @ return
* /
@Override
public User getUserByName(String name) {
try {
return userDao.getUserByName(name);
} catch (Exception e) {
throw new RuntimeException( "查询user异常" );
}
}
/ * *
* 保存user
* @param user
* @ return
* /
@Override
public Integer saveUser(User user) {
if (userDao.getUserByName(user.getName()) ! = null) {
throw new RuntimeException( "用户名已存在" );
}
try {
return userDao.saveUser(user);
} catch (Exception e) {
throw new RuntimeException( "保存用户异常" );
}
}
}
|
现在我们的Service写好了,想要单元测试一下,但是Dao是其他人开发的,目前还没有写好,那我们如何测试呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
public class UserServiceTest {
/ * *
* Mock测试:根据name查询user
* /
@Test
public void getUserByNameTest() {
/ / mock对象
final UserDao userDao = mock(UserDao. class );
final UserServiceImpl userService = new UserServiceImpl();
userService.setUserDao(userDao);
/ / stub调用
final User user = new User();
user.setName( "admin" );
user.setPassword( "pass" );
when(userDao.getUserByName( "admin" )).thenReturn(user);
/ / 执行待测试方法
final User user1 = userService.getUserByName( "admin" );
System.out.println( "查询结果:" + JacksonUtil.obj2json(user1)); / / 查询结果:{ "name" : "admin" , "password" : "pass" }
/ / 验证mock对象交互
verify(userDao).getUserByName(anyString());
/ / 验证查询结果
Assert.assertNotNull( "查询结果为空!" , user1);
Assert.assertEquals( "查询结果错误!" , "admin" , user1.getName());
}
/ * *
* Mock测试:保存user
* /
@Mock
private UserDao userDao;
@InjectMocks
private UserServiceImpl userService;
@Test
public void saveUserTest() throws Exception{
/ / 执行注解初始化
MockitoAnnotations.initMocks(this);
/ / mock对象stub操作
final User user = new User();
user.setName( "admin" );
user.setPassword( "pass" );
when(userDao.getUserByName( "admin" )).thenReturn(user).thenReturn(null);
when(userDao.saveUser( any (User. class ))).thenReturn( 1 );
/ / 验证用户名重复的情况
try {
userService.saveUser(user);
throw new Exception(); / / 走到这里说明验证失败
} catch (RuntimeException e) {
System.out.println( "重复用户名保存失败-测试通过" ); / / 重复用户名保存失败 - 测试通过
}
verify(userDao).getUserByName( "admin" );
/ / 验证正常保存的情况
user.setName( "user" );
final Integer integer = userService.saveUser(user);
System.out.println( "保存结果:" + integer); / / 保存结果: 1
Assert.assertEquals( "保存失败!" , 1 , integer.longValue());
verify(userDao).saveUser( any (User. class ));
verify(userDao, times( 2 )).getUserByName(anyString());
}
}
|
根据以上代码我们可以知道,当我们的待测类开发完成而依赖的类的实现还没有开发完成。此时,我们就可以用到我们的Mock测试,模拟我们依赖类的返回值,使我们的待测类与依赖类解耦。这样,我们就可以对我们的待测类进行单元测了。
四、参考文档及进一步学习~
- Mockito英文版javadoc:https://javadoc.io/static/org.mockito/mockito-core/3.3.3/org/mockito/Mockito.html
- Mockito中文文档(部分):https://blog.csdn.net/bboyfeiyu/article/details/52127551#35
- Mockito使用教程:https://www.cnblogs.com/Ming8006/p/6297333.html
- 参数捕获器使用:https://www.journaldev.com/21892/mockito-argumentcaptor-captor-annotation
- 利用ArgumentCaptor(参数捕获器)捕获方法参数进行验证:https://www.iteye.com/blog/hotdog-916364
- 改变mock返回值:https://www.huangyunkun.com/2014/10/25/mockito-deep-stub-with-enum/
- 五分钟了解Mockito:https://www.iteye.com/blog/liuzhijun-1512780
- 使用Mockito进行单元测试:https://www.iteye.com/blog/qiuguo0205-1443344
- JUnit + Mockito 单元测试:https://blog.csdn.net/zhangxin09/article/details/42422643
- Mockito中@Mock与@InjectMock:https://www.cnblogs.com/langren1992/p/9681600.html
- mockito中两种部分mock的实现,spy、callRealMethod:https://www.cnblogs.com/softidea/p/4204389.html
- Mockito 中被 Mocked 的对象属性及方法的默认值:https://www.cnblogs.com/fnlingnzb-learner/p/10635250.html
- 单元测试工具之Mockito:https://blog.csdn.net/qq_32140971/article/details/90598454
- 引入Mockito测试用@Spy和@Mock:https://blog.csdn.net/message_lx/article/details/83308114
- Mockito初探(含实例):https://www.iteye.com/blog/sgq0085-2031319
- 测试覆盖率统计:https://blog.csdn.net/lvyuan1234/article/details/82836052?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
- 测试覆盖率无法统计解决:https://blog.csdn.net/zhanglei082319/article/details/81536398
作者:京东零售 秦浩然
来源:京东云开发者社区 转载请注明来源