JUnit 是单元测试框架。Mockito 与 JUnit 不同,并不是单元测试框架(这方面 JUnit 已经足够好了),它是用于生成模拟对象或者直接点说,就是”假对象“的工具。一个典型的例子就是使用模拟对象来模拟数据库DAO层。在生产环境上是使用运行的数据库,但是在单元测试环境中完全可以用模拟对象来模拟数据,确保单元测试的正确条件。这样就不需要依赖于外部的数据。一般通常的做法就是联合 JUnit + Mockito 来进行测试。
入门
首先是配置 Mock 对象,
List mock = mock( List.class );
when( mock.get(0) ).thenReturn( 1 );
assertEquals( "预期返回1", 1, mock.get( 0 ) );
再例如,假设我们没有 Tomcat(虽然不太可能,假设吧!),使用 Mockito 模拟一个:
HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getParameter("a")).thenReturn("aaa");
其中 request 是模拟 HttpServletRequest 的对象,拥有 HttpServletRequest 的所有方法和属性。when(xxxx).thenReturn(yyyy); 是指定当执行了这个方法的时候,返回 thenReturn 的值,相当于是对模拟对象的配置过程,为某些条件给定一个预期的返回值。相信通过这个简单的例子你可以明白所谓 Mock 便是这么一回事。
这里就是" Stub 打桩"的概念了,所谓打桩,就是把所需的测试数据,适用于基于状态的(state-based)测试,关注的是输入和输出。
对方法设定返回异常,需指定类型,参见 这里 。
when(i.next()).thenThrow(new RuntimeException());
Mockito支持 迭代风格 的返回值设定,
- 第一种方式 when(i.next()).thenReturn("Hello").thenReturn("World");
- 第二种方式 when(i.next()).thenReturn("Hello", "World");
上面的例子等价于:
when(i.next()).thenReturn("Hello");
when(i.next()).thenReturn("World");
第一次调用i.next()将返回”Hello”,第二次的调用会返回”World”。
对 void 方法不返回值,所以不能 when(mock.someMethod()).thenReturn(value) 这样的语法,可以这样
doNothing().when(i).remove();
doThrow(Throwable) 模拟返回异常
doThrow(new RuntimeException()).when(i).remove();
迭代风格 doNothing().doThrow(new RuntimeException()).when(i).remove();,这样,第一次调用remove方法什么都不做,第二次调用抛出RuntimeException异常。
模拟参数argument matchers
这个比较方便,anyInt() 匹配任何 int 参数,这意味着参数为任意值,其返回值均是 element
when(mockedList.get(anyInt())).thenReturn("element"); System.out.println(mockedList.get(999));// 此时打印是element
若方法中的某一个参数使用了matcher,则所有的参数都必须使用 matcher。
自定义类型也可以,参加 这里 。
模拟 Servlet
就
验证
实话说,我刚接触也不是太明白,参见 《用mockito的verify来验证mock的方法是否被调用》 :
看mockito的api时,一直都不清楚veriry()这个方法的作用,因为如果我mock了某个方法,肯定是为了调用的啊。直到今天在回归接口测试用例的时候,发现有两个用例,用例2比用例1多了一个 mock 的步骤,不过最后的结果输出是一样的。由于代码做了修改,我重新 mock 后,其实用例2中对于的步骤是不会执行的,可测试还是通过了。仔细查看后,发现mock的方法没有被调用,所以用例2和用例1就变成一样的了。于是,就产生了这么个需求:单单通过结果来判断正确与否还是不够的,我还要判断是否按我指定的路径执行的用例。到这里,终于领略到了mockito的verify的强大威力,以下是示例代码:
若调用成功,则程序正常运行,反之则会报告: Wanted but not invoked:verify(mockedList).add("one"); 错误。
using mocks mockedList.add("one"); mockedList.add("two"); verify(mockedList).add("one"); // 注意,如果times不传入,则默认是1
据说是 Mockito 跟踪了所有的方法调用和参数的调用情况。verify() 可以验证方法的行为。查看另外一个的例子:
@Test
public void testMap() {
Map mock = Mockito.mock( Map.class );
Mockito.when( mock.get( "city" ) ).thenReturn( "深圳" );
// test code
assertEquals( "城市测试", "深圳", mock.get( "city" ) );
Mockito.verify(mock).get( Matchers.eq( "city" ) );
Mockito.verify( mock, Mockito.times( 2 ) );
}
更多验证的例子参见 这里 。
其他高级话题
如果没有 JUnit,可以使用 Mockito 的 @Before 的注解,进行一些前期的初始化工作,
public class ArticleManagerTest {
@Mock private ArticleCalculator calculator;
@Mock private ArticleDatabase database;
@Mock private UserProvider userProvider;
@Before public void setup() {
MockitoAnnotations.initMocks(testClass);
}
}
如果有 JUnit,则无需 @Before,但要修改 JUnit 默认容器,
@RunWith(MockitoJUnitRunner.class)
public class ExampleTest {
@Mock private List list;
@Test public void shouldDoSomething() {
list.add(100);
}
}
在 JUnit 中有很多个 Runner ,他们负责调用你的测试代码,每一个 Runner 都有各自的特殊功能,你要根据需要选择不同的 Runner 来运行你的测试代码。