使用Mockito进行单元测试
Mockito简介
Mockito是一个Java仿冒框架,所谓仿冒,就是创建一个“虚假”的类,来模拟一个类的行为。之所以需要“仿冒”,可能出于下列考虑:
- 某个类已经进入单元测试,而协作类尚未开发完成。此时需要快速模拟出协作类,避免耽误测试进度
- 协作类的实现可能存在缺陷,这是可以使用仿冒隔离其影响,仿冒严格的按照接口规约返回处理结果
- 模拟难以在单元测试阶段获得实例的对象,这些对象往往和环境、容器相关,例如HttpServletRequest
Mockito关注三件事件:Mock的创建、验证(verification)和打桩(stubbing)。所谓验证,就是断言预期的行为(方法调用)发生;所谓打桩,就是设置Mock在特定条件(入参)下的行为(返回值、异常)。
功能说明
简单的例子
下面的小例子说明何为验证、打桩:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import static org.mockito.Mockito.*; import static org.junit.Assert.*; //制作一个仿冒 Map<String, Object> map = mock( Map.class ); //对仿冒进行调用 map.put( "Key", 1024 ); map.clear(); //验证:仿冒的某个方法以指定的参数被调用一次 verify( map ).put( "Key", new Integer( 1024 ) ); //只进行equals匹配 //验证:仿冒的某个方法至少被调用一次 verify( map, atLeast( 1 ) ).clear(); //打桩:尚未打桩的入参调用,返回null assertEquals( null, map.get( "Any" ) ); //打桩:当以Hello为键获取值时,返回World when( map.get( "Hello" ) ).thenReturn( "World" ); assertEquals( "World", map.get( "Hello" ) ); |
可以看到,都Mock的每一种调用,我们都可以监控到;相应的Mock可以迅速、准确的模拟出真实对象的行为, 而忽略任何行为的实现细节。
未打桩前,Mock这样处理返回值:
- 返回Object的方法,Mock默认返回null
- 返回集合的方法,Mock默认返回空集合
- 返回数字的方法,Mock默认返回0
- 返回布尔的方法,Mock默认返回false
对Mock的打桩可以进行多次,后面的会覆盖前面的打桩。
参数匹配
对Mock进行打桩或者验证时时,可以有多种匹配入参的方式:
1 2 3 4 5 6 7 8 9 10 11 12 |
List list = mock( List.class ); when( list.get( anyInt() ) ).thenReturn( null );//任何整数参数 when( list.addAll( anyInt(), anyCollection() ) ).thenReturn( null );//两个任意参数 //实现参数匹配器接口,进行任意复杂度的匹配 when( list.get( argThat( new ArgumentMatcher<Integer>() { @Override public boolean matches( Object item ) { Integer index = (Integer) item; return index < 100; } } ) ) ).thenReturn( null ); |
参数捕获
1 2 3 4 5 6 |
List list = mock( List.class ); ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass( Integer.class ); list.add( 1 );//这个参数会被捕获 verify( list ).get( argument.capture() ); //执行捕获 //后续对参数进行进一步验证 assertEquals( new Integer( 1 ), argument.getValue() ); |
返回值设定
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 |
List list = mock( List.class ); //返回固定值 when( list.get( 0 ) ).thenReturn( 10 ); //设置每次调用返回不同值 when( list.get( 0 ) ).thenReturn( 1, 2, 3 ); //抛出异常,不返回 when( list.get( 0 ) ).thenThrow( new RuntimeException() ); //依据入参决定返回值 when( list.get( anyInt() ) ).then( new Answer<Object>() { public Object answer( InvocationOnMock invocation ) throws Throwable { return "El-" + invocation.getArguments()[0]; } } ); //在部分仿冒时,调用真实方法 when( list.get( 0 ) ).thenCallRealMethod(); //连续仿冒 when( list.get( 0 ) ) .thenReturn( 1 ) //第一次调用的返回值 .thenReturn( 2 )//第二次调用的返回值 .thenThrow( new RuntimeException() ); //以后调用的返回值 //设置未打桩方法的默认返回值 mock( List.class, new Answer<Object>() { @Override public Object answer( InvocationOnMock invocation ) throws Throwable { return null; } } ); |
void方法打桩
1 2 3 4 |
//什么都不作 doNothing().when( list ).clear(); //抛出异常 doThrow( new RuntimeException() ).when( list ).clear(); |
调用次数验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
List list = mock( List.class ); //验证基于某个精确实参的调用发生的次数 verify( list, times( 1 ) ).add( 1 ); //一次 verify( list, times( 2 ) ).add( 2 ); //二次 verify( list, atLeastOnce() ).add( 1 ); //至少一次 verify( list, atLeast( 3 ) ).add( 3 ); //至少三次 verify( list, atMost( 3 ) ).add( 3 ); //至多三次 //参数匹配也适用于验证的时候: verify( list, atMost( 3 ) ).add( argThat( new ArgumentMatcher<Object>() { public boolean matches( Object argument ) { return false; } } ) ); //断言Mock上没有发生任何未验证的调用 verifyNoMoreInteractions( list ); |
监控真实对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
List<Integer> list = new ArrayList<Integer>(); List<Integer> spy = spy( list ); //对真实对象进行打桩 when( spy.size() ).thenReturn( 100 ); //另外一种打桩方式,防止异常 doReturn( 1 ).when( spy ).get( 0 ); //下面这种方式是不行的,因为未打桩前会调用真实方法,导致IndexOutOfBoundsException when( spy.get( 0 ) ).thenReturn( 1 ); spy.add( 1 );//调用真实的方法 spy.size();//调用仿冒的方法 verify( spy ).add( 1 ); //验证调用了一次 |
部分仿冒
有两种方式进行部分仿冒:Spy一个对象,或者Mock一个类(非接口):
1 2 3 4 |
List list = spy(new LinkedList()); List list = mock(LinkedList.class); //调用真实对象的方法 when(list.clear()).thenCallRealMethod(); |
注解的支持
1 2 3 4 5 6 7 8 9 10 |
//如果要启用注解支持,可以使用该运行类 //或者手工调用MockitoAnnotations.initMocks(this) @RunWith ( MockitoJUnitRunner.class ) public class MockitoTest { //自动创建Mock @Mock private List<Object> list; } |
其它
1 2 3 4 5 6 7 |
List mock = mock(List.class); when(mock.size()).thenReturn(10); reset(mock); //重置仿冒 //单行调用即完成的Mock Car boringStubbedCar = when(mock(Car.class).shiftGear()).thenThrow(EngineNotStarted.class).getMock(); |
Leave a Reply