Mockito 初探
一. 简介
1.背景
Mockito是一个流行的Mocking(模拟测试)框架,通过使用Mocking框架,可以尽可能使unit test独立的。unit test保持独立的好处不在这里讨论。
官方文档: http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html
2.Stub对象和Mock对象的区别
Stub对象用来提供测试时所需要的测试数据,可以对各种交互设置相应的回应。例如我们可以设置方法调用的返回值等等。Mockito中when(…).thenReturn(…)。这样的语法便是设置方法调用的返回值。另外也可以设置方法在何时调用会抛异常等。
Mock对象用来验证测试中所依赖对象间的交互是否能够达到预期。Mockito中用verify(…).methodXxx(…) 语法来验证 methodXxx 方法是否按照预期进行了调用。
3.依赖的配置
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>1.9.5</version> <scope>test</scope> </dependency>
二.基础
1.Mocktio包的引入
(1) 为了方便的使用Mockito下的静态方法,在Eclipse 点击 preferences > Java > Editor > Content assist > Favorites > New Type。加入org.mockito.Mockito;
(2) preferences > Java > Code Style > Organize Import, 设置Number of static imports needed of * ,这里设置为1。
2.被测试目标类
为了方便说明,以下面这个类作为测试的目标
@Service @Transactional public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override public User getUserByLoginName(String loginName) { try { return userDao.findUserByLoginName(loginName); } catch (Exception e) { throw new RuntimeException("查找用户失败"); } } @Override public String save(User user) { User oldUser = userDao.findUserByLoginName(user.getLoginName()); if (oldUser != null) { throw new RuntimeException("已存在相同登录名"); } try { userDao.save(user); return user.getId(); } catch (Exception e) { throw new RuntimeException("新增用户失败", e); } } }
3.创建Mock对象
(1)普通方法
普通方法中,被测试类可以写接口,因为可以手动new出实现类
public class UserServiceTest { private UserService userService; private UserDao mockUserDao; @Before public void setUp() { mockUserDao = mock(UserDao.class); userService = new UserServiceImpl(); userService.setUserDao(mockUserDao); }(2)通过注解
被@InjectMocks注解标注的属性,可以自动注入标记@Mock、@Spy等注解的属性值
@InjectMocks标注的属性不能使用接口,因为@InjectMocks不能传入参数指明实现类
public class UserServiceTest { @InjectMocks private UserServiceImpl userService; @Mock private UserDao mockUserDao; @Before public void setUp() { MockitoAnnotations.initMocks(this); }
4.简单例子
@Test public void getUserByLoginName() { User rtnUser = new User(); rtnUser.setLoginName("admin"); // Stud 设置方法调用的预期返回 when(mockUserDao.findUserByLoginName(anyString())).thenReturn(rtnUser); User user = userService.getUserByLoginName("admin"); // Mock 验证方法调用 verify(mockUserDao, times(1)).findUserByLoginName(anyString()); // assert 返回值是否和预期一样 assertThat(user).isNotNull(); assertThat(user.getLoginName()).isEqualTo("admin"); }
(1) 设置方法调用的预期返回
通过when(mock.someMethod()).thenReturn(value),来设定mock对象某个方法调用时的返回值。这里我们设定调用传入任意字符串调用findUserByLoginName方法,返回rtnUser。
(2)验证方法调用
mock对象一旦创建,就会自动记录自己的交互行为。通过verify(mock).someMethod()方法,来验证方法是否被调用。这里我们验证了mockUserDao的findUserByLoginName方法被调用,并且通过times(1),验证是否被调用一次。注意,如果times不传入,则默认是1。
(3) assert返回值
最后通过assert断言返回值的正确性。这里使用的assertJ,具体内容可以参看 http://sgq0085.iteye.com/blog/2030609
三. mockito的使用
1.Mock 对象的期望行为及返回值设定
注意,Mockito的Stubbing有两种语法,并支持迭代。
(1)Stubbing对方法设定返回值
when(mock.someMethod()).thenReturn(value1).thenReturn(value2); when(mock.someMethod()).thenReturn(value1, value2);
上面两种方式等同于
when(mock.someMethod()).thenReturn(value1); when(mock.someMethod()).thenReturn(value2);
另一种风格doReturn,主要用于spy对象的情况下。
doReturn(value1).doReturn(value2).when(mock).someMethod();
对 void 方法进行方法预期设定
doNothing().when(mock).someMethod();
(2)对方法设定返回异常
when(mock.someMethod()).thenThrow(new RuntimeException());
对 void 方法进行方法预期设定
doThrow(new RuntimeException()).when(mock).someMethod();
(3)Argument Matcher(参数匹配器)
Mockito提供了参数匹配器,用于灵活的匹配参数。
比如 any(User.class),匹配任意User对象;anyString()匹配任意字符串;anyInt()匹配任意int型。
2.Mock对象的行为验证
看一下下面的例子
@Test public void verifyTestTest() { List<String> mock = mock(List.class); List<String> mock2 = mock(List.class); when(mock.get(0)).thenReturn("Hello"); when(mock.get(1)).thenReturn("World"); mock.get(0); mock.get(1); // 验证指定方法被调用一次 verify(mock).get(0); // 验证指定方法没有被调用 verify(mock, never()).get(3); // 验证get方法在100毫秒内被调用两次 verify(mock, timeout(100).times(2)).get(anyInt()); // 通过验证方法的执行顺序 InOrder inOrder = inOrder(mock, mock2); inOrder.verify(mock).get(0); inOrder.verify(mock).get(1); inOrder.verify(mock2, never()).get(1); // 查询多余的方法调用 mock所有调用的方法都已经被验证 verifyNoMoreInteractions(mock); // 查询没有交互的mock对象 verifyZeroInteractions(mock2); // 创建ArgumentCaptor(参数捕获器)用于捕获方法参数进行验证 ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class); // 该方法被调用多次 只能捕捉到最后一次参数 verify(mock, times(2)).get(argument.capture()); assertThat(argument.getValue()).isEqualTo(1); }
(1)验证方法的调用次数
通过 verify(mock,times(?)).someMethod()验证方法的调用次数,是常用的验证方式,包括可以验证指定明确的方法被调用次数、某方法未被调用。或一个方法总体被调用次数。
Mockito除了提供times(N)方法供我们调用外,还提供了很多可选的方法:
never() 没有被调用,相当于times(0)
atLeast(N) 至少被调用N次
atLeastOnce() 相当于atLeast(1)
atMost(N) 最多被调用N次
(2) 超时验证
通过timeout,并制定毫秒数验证超时。注意,如果被调用多次,times还是需要的。
(3) 方法调用顺序
通过InOrder对象,验证方法的执行顺序,如上例子中,如果mock的get(0)和get(1)方法反过来则测试不通过。这里mock2其实没有被调用过。所以不需要些。
(4) verifyNoMoreInteractions
查询是否存在被调用,但未被验证的方法,如果存在则抛出异常。这里因为验证了get(anyInt()),相当于所有的get方法被验证,所以通过。
(5) verifyZeroInteractions
查询对象是否未产生交互,如果传入的mock对象的方法被调用过,则抛出异常。这里mock2的方法没有被调用过,所有通过。
(6) 利用ArgumentCaptor(参数捕获器)捕获方法
参数进行验证通过 ArgumentCaptor 对象的forClass(Class<T> clazz)方法来构建ArgumentCaptor对象。然后便可在验证时对方法的参数进行捕获,最后验证捕获的参数值。如果方法有多个参数都要捕获验证,那就需要创建多个ArgumentCaptor对象处理。
当某个对象进行了多次调用后,比如mock对象。这时调用argument.getValue()获取到的是最后一次调用的参数。如果要获取所有的参数值可以调用argument.getAllValues(),它将返回参数值的List。
3.Spy-对象的监视
Mock 对象只能调用stubbed 方法,调用不了它真实的方法。但Mockito 可以监视一个真实的对象,这时对它进行方法调用时它将调用真实的方法,同时也可以stubbing 这个对象的方法让它返回我们的期望值。另外不论是否是真实的方法调用都可以进行verify验证。和创建mock对象一样,对于final类、匿名类和Java的基本类型是无法进行spy的。
监视对象
监视一个对象需要调用spy(T object)方法,如下面的代码中spy变量就在监视LinkedList实例。
List spy = spy(new LinkedList());
被监视对象的Stubbing
stubbing 被监视对象的方法时要慎用when(Object)。比如下面的代码:
List spy = spy(new LinkedList()); // IndexOutOfBoundsException (the list is yet empty) when(spy.get(0)).thenReturn("foo"); // You have to use doReturn() for stubbing doReturn("foo").when(spy).get(0);
这时,when方法参数中spy.get(0),调用的是真实list对象的get(0),这会产生IndexOutOfBoundsException异常,所以这时需要用到doReturn方法来设置返回值。
下面是一个官方给出的例子,仅供参考:
@Test public void spyTest() { List list = new LinkedList(); List spy = spy(list); // optionally, you can stub out some methods: when(spy.size()).thenReturn(100); // using the spy calls real methods spy.add("one"); spy.add("two"); // prints "one" - the first element of a list System.out.println(spy.get(0)); // size() method was stubbed - 100 is printed System.out.println(spy.size()); // optionally, you can verify verify(spy).add("one"); verify(spy).add("two"); }
4.总结
最后,根据最开始的userService给出两个基于mockito测试方法来体现上述提到过的知识点。仅用来展示用法。
@Test public void save() { User user = new User(); user.setLoginName("admin"); // 第一次调用findUserByLoginName返回user 第二次调用返回null when(mockUserDao.findUserByLoginName(anyString())).thenReturn(user).thenReturn(null); try { // 测试如果重名会抛出异常 userService.save(user); // 如果没有抛出异常测试不通过 failBecauseExceptionWasNotThrown(RuntimeException.class); } catch (ServiceException se) { } verify(mockUserDao).findUserByLoginName("admin"); // userService.save(user); user.setPassword("123456"); String userId = userService.save(user); // 断言返回结果 assertThat(userId).isNotEmpty().hasSize(32); verify(mockUserDao, times(2)).findUserByLoginName(anyString()); verify(mockUserDao).save(any(User.class)); } @Test public void save2() { User user = new User(); user.setLoginName("admin"); user.setPassword("123456"); userService.save(user); // 通过ArgumentCaptor(参数捕获器) 对传入参数进行验证 ArgumentCaptor<User> argument = ArgumentCaptor.forClass(User.class); verify(mockUserDao).save(argument.capture()); assertThat("admin").isEqualTo(argument.getValue().getLoginName()); // stub 调用save方法时抛出异常 doThrow(new ServiceException("测试抛出异常")).when(mockUserDao).save(any(User.class)); try { userService.save(user); failBecauseExceptionWasNotThrown(RuntimeException.class); } catch (ServiceException se) { } }
永久链接: http://sgq0085.iteye.com/blog/2031319
上一篇: PHP搜索数组的实现方法介绍
下一篇: (转)【ios】单元测试