import org.junit.Test;
import org.mockito.Matchers;
import org.mockito.Mockito; import java.util.List;
import java.util.Map; import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Mockito.*; /**
* Created by MyWorld on 2016/1/26.
*/
public class MockitoDemo { @Test
public void mockitoMapDemo1() {
Map mockedMap = Mockito.mock(Map.class);
when(mockedMap.get("city")).thenReturn("广州");
Object cityValue = mockedMap.get("city");
assertThat(cityValue.toString(), is("广州"));
verify(mockedMap).get(Matchers.eq("city"));
verify(mockedMap, times(2));
} @Test
public void mockitoMapDemo2() {
Map mockedMap = Mockito.mock(Map.class);
// when(mockedMap.put(anyInt(), anyString())).thenReturn("world");
mockedMap.put(1, "hello");
verify(mockedMap).put(anyInt(), eq("hello"));
} @Test
public void mockitoListDemo() {
List mockedList = Mockito.mock(List.class);
mockedList.add("one");
mockedList.add("two");
verify(mockedList).add("one");
verify(mockedList, times(2)).add(anyString());
}
}

JUnit 是单元测试框架。Mockito 与 JUnit 不同,并不是单元测试框架(这方面 JUnit 已经足够好了),它是用于生成模拟对象或者直接点说,就是”假对象“的工具。两者定位不同,所以一般通常的做法就是联合 JUnit + Mockito 来进行测试。

入门

首先是配置 Mock 对象,看看例子怎么写的。

        List mockedList = Mockito.mock(List.class);
when(mockedList.get(0)).thenReturn(1);
assertEquals("Descriptive information ", 1, mockedList.get(0));

其中 mock 是模拟 List 的对象,拥有 List 的所有方法和属性。when(xxxx).thenReturn(yyyy); 是指定当执行了这个方法的时候,返回 thenReturn 的值,相当于是对模拟对象的配置过程,为某些条件给定一个预期的返回值。相信通过这个简单的例子你可以明白所谓 Mock 便是这么一回事。

我们看到 List 为 Java.util.List 是接口,并不是实现类,但这不妨碍我们使用它作为我们的“打桩”对象,——当然你也可以使用实现类,传入 mock(obj) 方法中。
这里提到的是"打桩(Stub,也有人称其为“存根”)"的概念,是一个形象的说法,就是把所需的测试数据塞进对象中,
适用于基于状态的(state-based)测试关注的是输入和输出
Mockito 中 when(…).thenReturn(…)  这样的语法来定义对象方法和参数(输入),然后在 thenReturn 中指定结果(输出)。此过程称为 Stub 打桩。
一旦这个方法被 stub 了,就会一直返回这个 stub 的值。

打桩需要注意以下几点

  • 对于 static 和 final 方法, Mockito 无法对其 when(…).thenReturn(…) 操作。
  • 当我们连续两次为同一个方法使用 stub 的时候,他只会只用最新的一次。

mock 对象会覆盖整个被 mock 的对象,因此没有 stub 的方法只能返回默认值。又因为,我们 mock 一个接口的时候,很多成员方法只是一个签名,并没有实现,这就要我们手动写出这些实现方法啦。典型地,我们模拟一个 request 请求对象,你被测试的代码中使用了 HttpSerevletRequest 什么方法,就要写出相应的实现方法!

这里“打桩”之后,我们执行 request.getParamter("foo") 就会返回 boo,如果不这样设定,Mockito 就会返回默认的 null,也不会报错说这个方法找不到。
mock 实例默认的会给所有的方法添加基本实现:返回 null 或空集合,或者 0 等基本类型的值。
这取决于方法返回类型,如 int 会返回 0,布尔值返回 false。对于其他 type 会返回 null。

打桩支持迭代风格的返回值设定,例如,

上述我们一直在讨论被测试的方法都有返回值的,那么没有返回值的 void 方法呢?也是测试吗?答案是肯定的。——只不过 Mockito 要求你的写法上有不同,因为都没返回值了,调用 thenReturn(xxx) 肯定不行,取而代之的写法是,

  1. doNothing().when(obj).notify();
  2. // 或直接
  3. when(obj).notify();

Mockito 还能对被测试的方法强行抛出异常,

  1. when(i.next()).thenThrow(new RuntimeException());
  2. doThrow(new RuntimeException()).when(i).remove(); // void 方法的
  3. // 迭代风格
  4. doNothing().doThrow(new RuntimeException()).when(i).remove(); // 第一次调用 remove 方法什么都不做,第二次调用抛出 RuntimeException 异常。

如需指定异常类型,参见这里

模拟传入的参数 argument matchers

拿上面的例子说,其中一个问题,

这里 getParameter("foo") 这里我们是写死参数 foo 的,但是如果我不关心输入的具体内容,可以吗?可以的,最好能像正则表达式那样,/w+ 表示任意字符串是不是很方便,不用考虑具体什么参数,只要是 字符串 型的参数,就可以打桩。如此方便的想法 Mockito 也考虑到了,提供 argument matchers 机制,例如 anyString() 匹配任何 String 参数,anyInt() 匹配任何 int 参数,anySet() 匹配任何 Set,any() 则意味着参数为任意值。例子如下,

when(mockedList.get(anyInt())).thenReturn("element");

System.out.println(mockedList.get(999));// 此时打印是 element

再进一步,自定义类型也可以,如 any(User.class),另,参见《学习 Mockito - 自定义参数匹配器》 和 这里 和 这里

获取返回的结果

一个问题,thenReturn 是返回结果是我们写死的。如果要让被测试的方法不写死,返回实际结果并让我们可以获取到的——怎么做呢?
有时我们需要自定义方法执行的返回结果,Answer 接口就是满足这样的需求而存在的。

例如模拟常见的 request.getAttribute(key),由于这本来是个接口,所以连内部实现都要自己写了。此次通过 Answer 接口获取参数内容。

  1. final Map<String, Object> hash = new HashMap<String, Object>();
  2. Answer<String> aswser = new Answer<String>() {
  3. public String answer(InvocationOnMock invocation) {
  4. Object[] args = invocation.getArguments();
  5. return hash.get(args[0].toString()).toString();
  6. }
  7. };
  8. when(request.getAttribute("isRawOutput")).thenReturn(true);
  9. when(request.getAttribute("errMsg")).thenAnswer(aswser);
  10. when(request.getAttribute("msg")).thenAnswer(aswser);

利用 InvocationOnMock 提供的方法可以获取 mock 方法的调用信息。下面是它提供的方法:

  • getArguments() 调用后会以 Object 数组的方式返回 mock 方法调用的参数。
  • getMethod() 返回 java.lang.reflect.Method 对象
  • getMock() 返回 mock 对象
  • callRealMethod() 真实方法调用,如果 mock 的是接口它将会抛出异常

void 方法可以获取参数,只是写法上有区别,

  1. doAnswer(new Answer<Object>() {
  2. public Object answer(InvocationOnMock invocation) {
  3. Object[] args = invocation.getArguments();
  4. // Object mock = invocation.getMock();
  5. System.out.println(args[1]);
  6. hash.put(args[0].toString(), args[1]);
  7. return "called with arguments: " + args;
  8. }
  9. }).when(request).setAttribute(anyString(), anyString());

其实就是一个回调,——如果不是接口,是实现类的话,估计不用自己写实现。

验证 Verify

前面提到的 when(……).thenReturn(……) 属于状态测试,某些时候,测试不关心返回结果,而是侧重方法有否被正确的参数调用过,这时候就应该使用 验证方法了。从概念上讲,就是和状态测试所不同的“行为测试”了。

一旦使用 org.mockito.Mockito.mock() 对模拟对象打桩,意味着 Mockito 会记录着这个模拟对象调用了什么方法,还有调用了多少次。
最后由用户决定是否需要进行验证,即 org.mockito.Mockito.verify() 方法。

verify() 说明其作用的例子:

        List mockedList = Mockito.mock(List.class);
mockedList.add("one");
mockedList.add("two");
verify(mockedList).add("one");// 如果times不传入,则默认是1
verify(mockedList, times(2)).add(anyString());
        Map mockedMap = Mockito.mock(Map.class);
when(mockedMap.get("city")).thenReturn("广州");
Object cityValue = mockedMap.get("city");
assertThat(cityValue.toString(), is("广州"));
// 关注参数有否传入
verify(mockedMap).get(Matchers.eq("city"));
// 关注调用的次数
verify(mockedMap, times(2));

也就是说,这是对历史记录作一种回溯校验的处理。

这里补充一个学究的问题,所谓 Mock 与 Stub 打桩,其实它们之间不能互为其表。但 Mockito 语境中则 Stub 和 Mock 对象同时使用的。因为它既可以设置方法调用返回值,又可以验证方法的调用。有关 stub 和 mock 的详细论述请见 Martin Fowler 大叔的文章《Mocks Aren't Stub》

Mockito 除了提供 times(N) 方法供我们调用外,还提供了很多可选的方法:

  • never() 没有被调用,相当于 times(0)
  • atLeast(N) 至少被调用 N 次
  • atLeastOnce() 相当于 atLeast(1)
  • atMost(N) 最多被调用 N 次

verify 也可以像 when 那样使用模拟参数,若方法中的某一个参数使用了matcher,则所有的参数都必须使用 matcher

        Map mockedMap = Mockito.mock(Map.class);
mockedMap.put("", "");
String newValue = "newValue";
String oldValue = "oldValue";
//若方法中的某一个参数使用了matcher,则所有的参数都必须使用 matcher.
// 否则会报异常:org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
// When using matchers, all arguments have to be provided by matchers.
when(mockedMap.put(anyInt(), eq(newValue))).thenReturn(oldValue);//mock a put operation
Object oldValueForPut = mockedMap.put(1, newValue);//get the mock value
assertThat(oldValueForPut.toString(), is(oldValue));//assert the mock operation verify(mockedMap).put(anyInt(), eq(newValue));//verify whether the mock operation execute or not mockedMap.put(1, "hello");
verify(mockedMap).put(anyInt(), eq("hello"));

 

其他高级用法,详见《学习 Mockito - Mock对象的行为验证》,主要特性如下,

  • 参数验证,详见《利用 ArgumentCaptor(参数捕获器)捕获方法参数进行验证》
  • 超时验证,通过 timeout,并制定毫秒数验证超时。注意,如果被调用多次,times 还是需要的。
  • 方法调用顺序 通过 InOrder 对象,验证方法的执行顺序,如上例子中,如果 mock 的 get(0) 和 get(1) 方法反过来则测试不通过。这里 mock2 其实没有被调用过。所以不需要些。
  • verifyNoMoreInteractions 查询是否存在被调用,但未被验证的方法,如果存在则抛出异常。这里因为验证了get(anyInt()),相当于所有的 get 方法被验证,所以通过。
  • verifyZeroInteractions 查询对象是否未产生交互,如果传入 的 mock 对象的方法被调用过,则抛出异常。这里 mock2 的方法没有被调用过,所有通过。

参见《用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"); 错误。

感觉 verify 会用的比较少。

Spy

spy 的意思是你可以修改某个真实对象的某些方法的行为特征,而不改变他的基本行为特征,这种策略的使用跟 AOP 有点类似。下面举官方的例子来说明:

  1. List list = new LinkedList();
  2. List spy = spy(list);
  3. //optionally, you can stub out some methods:
  4. when(spy.size()).thenReturn(100);
  5. //using the spy calls <b>real</b> methods
  6. spy.add("one");
  7. spy.add("two");
  8. //prints "one" - the first element of a list
  9. System.out.println(spy.get(0));
  10. //size() method was stubbed - 100 is printed
  11. System.out.println(spy.size());
  12. //optionally, you can verify
  13. verify(spy).add("one");
  14. verify(spy).add("two");

可以看到 spy 保留了 list 的大部分功能,只是将它的 size() 方法改写了。不过 spy 在使用的时候有很多地方需要注意,一不小心就会导致问题,所以不到万不得已还是不要用 spy。

总结例子

出处在这里

  1. @Test
  2. public void save() {
  3. User user = new User();
  4. user.setLoginName("admin");
  5. // 第一次调用findUserByLoginName返回user 第二次调用返回null
  6. when(mockUserDao.findUserByLoginName(anyString())).thenReturn(user).thenReturn(null);
  7. try {
  8. // 测试如果重名会抛出异常
  9. userService.save(user);
  10. // 如果没有抛出异常测试不通过
  11. failBecauseExceptionWasNotThrown(RuntimeException.class);
  12. } catch (ServiceException se) {
  13. }
  14. verify(mockUserDao).findUserByLoginName("admin");
  15. // userService.save(user);
  16. user.setPassword("123456");
  17. String userId = userService.save(user);
  18. // 断言返回结果
  19. assertThat(userId).isNotEmpty().hasSize(32);
  20. verify(mockUserDao, times(2)).findUserByLoginName(anyString());
  21. verify(mockUserDao).save(any(User.class));
  22. }
  23. @Test
  24. public void save2() {
  25. User user = new User();
  26. user.setLoginName("admin");
  27. user.setPassword("123456");
  28. userService.save(user);
  29. // 通过ArgumentCaptor(参数捕获器) 对传入参数进行验证
  30. ArgumentCaptor<User> argument = ArgumentCaptor.forClass(User.class);
  31. verify(mockUserDao).save(argument.capture());
  32. assertThat("admin").isEqualTo(argument.getValue().getLoginName());
  33. // stub 调用save方法时抛出异常
  34. doThrow(new ServiceException("测试抛出异常")).when(mockUserDao).save(any(User.class));
  35. try {
  36. userService.save(user);
  37. failBecauseExceptionWasNotThrown(RuntimeException.class);
  38. } catch (ServiceException se) {
  39. }
  40. }

其他高级话题

如果没有 JUnit,可以使用 Mockito 的 @Before 的注解,进行一些前期的初始化工作,

  1. public class ArticleManagerTest {
  2. @Mock
  3. private ArticleCalculator calculator;
  4. @Mock
  5. private ArticleDatabase database;
  6. @Mock
  7. private UserProvider userProvider;
  8. @Before
  9. public void setup() {
  10. MockitoAnnotations.initMocks(testClass);
  11. }
  12. }

如果有 JUnit,则无需 @Before,但要修改 JUnit 默认容器,

  1. @RunWith(MockitoJUnitRunner.class)
  2. public class ExampleTest {
  3. @Mock 
        private List list;
  4. @Test public void shouldDoSomething() {
  5. list.add(100);
  6. }
  7. }

在 JUnit 中有很多个 Runner ,他们负责调用你的测试代码,每一个 Runner 都有各自的特殊功能,你要根据需要选择不同的 Runner 来运行你的测试代码。

----------------------------------------------------

貌似 Mockito 的注解都比较强大,有待以后再看看:

《学习Mockito - Mockito对Annotation的支持》, http://jilen.iteye.com/blog/1427368

参见资源:

1自动生成Mock类

在需要Mock的属性上标记@Mock注解,然后@RunWith(MockitoJUnitRunner.class)或者在setUp()方法中显示调用MockitoAnnotations.initMocks(this);生成Mock类即可。

3 Mock方法定制再也不用录制、播放了

Mockito的Mock方法定制可读性很强,而且也不需要像EasyMock那样录制播放,定制后就可以使用。
例如:
when(userDao.selectAll()).
thenReturn(Collections.<UserDomain>emptyList());

http://blog.csdn.net/dc_726/article/details/8568537

JUnit + Mockito 单元测试(二)(good)的更多相关文章

  1. JUnit + Mockito 单元测试

    原 JUnit + Mockito 单元测试(二) 2015年01月05日 17:26:02 sp42a 阅读数:60755 版权声明:本文为博主原创文章,未经博主允许不得转载. https://bl ...

  2. JUnit + Mockito 单元测试(二)

    摘自: http://blog.csdn.net/zhangxin09/article/details/42422643 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] 入门 ...

  3. 基于Springboot+Junit+Mockito做单元测试

    前言 前面的两篇文章讨论过< 为什么要写单元测试,何时写,写多细 >和<单元测试规范>,这篇文章介绍如何使用Springboot+Junit+Mockito做单元测试,案例选取 ...

  4. Android之如何使用JUnit进行单元测试

    转的:http://www.blogjava.net/qileilove/archive/2014/05/19/413824.html Android中如何使用JUnit进行单元测试 在我们日常开发a ...

  5. Spring(3)—— Junit框架单元测试

    Junit主要用于单元测试,即白盒测试.它是一个开源的由JAVA开发的一个用于测试的框架. Junit的几个基本概念:TestCase,TestSuite,TestFixtrue TestCase:代 ...

  6. Intellij Idea系列之导Jar包与编写单元测试(二)

     Intellij Idea系列之导Jar包与编写单元测试(二) 一.初衷 对于很多的初学者来说,Intellij如何导入jar包感到很迷惑,甚至在网上搜过相关文章之后还是云里雾里,本博客通过图文并茂 ...

  7. 使用Junit进行单元测试

    使用Junit进行单元测试 一.目的和要求 JUnit是一款由Erich Gamma(<设计模式>的作者)和Kent Beck(极限编程的提出者)编写的开源的回归测试框架,供Java编码人 ...

  8. Android中如何使用JUnit进行单元测试 eclipse

    Android中如何使用JUnit进行单元测试 在我们日常开发android app的时候,需要不断地进行测试,所以使用JUnit测试框架显得格外重要,学会JUnit可以加快应用的开发周期. Andr ...

  9. SpringBoot重点详解--使用Junit进行单元测试

    目录 添加依赖与配置 ApplicationContext测试 Environment测试 MockBean测试 Controller测试 情况一 情况二 方法一 方法二 本文将对在Springboo ...

随机推荐

  1. Android性能优化之App应用启动分析与优化

    前言: 昨晚新版本终于发布了,但是还是记得有测试反馈app启动好长时间也没进入app主页,所以今天准备加个班总结一下App启动那些事! app的启动方式: 1.)冷启动      当启动应用时,后台没 ...

  2. 在myeclipse文件中如何创建properties类型的文件,从而连接数据库

     File->New->File->点击->在编辑处出输入:文件名.properties  文件的主要功能连接数据库,例如: driver=oracle.jdbc.Oracle ...

  3. 利用Hadoop实现超大矩阵相乘之我见(二)

    前文 在<利用Hadoop实现超大矩阵相乘之我见(一)>中我们所介绍的方法有着“计算过程中文件占用存储空间大”这个缺陷,本文中我们着重解决这个问题. 矩阵相乘计算思想 传统的矩阵相乘方法为 ...

  4. JEasyPoi 2.1.4 (Jeecg订制) 版本发布,Excel 和 Word 简易工具类

    JEasyPoi 2.1.4 (jeecg订制)版本发布,EasyPoi Excel 和 Word 简易工具类 easypoi 功能如同名字easy,主打的功能就是容易,让一个没见接触过poi的人员 ...

  5. Springmvc 整合 jetbrick 实例

    应用环境:  <jetbrick.version>1.2.8</jetbrick.version> <antlr4-runtime.version>4.2.2< ...

  6. linux中合并多个文件内容到一个文件的例子

    尊敬的用户您好,从即日起 导入 及 导出 功能已经下线,请到阿里云官方数据库管理平台 iDB Cloud 使用该功能! 继续在 iDB Cloud 中发现导出的数据库文件是按照每个表生成的SQL文件, ...

  7. Spring quartz Job不能依赖注入,Spring整合quartz Job任务不能注入

    Spring quartz Job不能依赖注入,Spring整合quartz Job任务不能注入 Spring4整合quartz2.2.3中Job任务使用@Autowired不能注入 >> ...

  8. Java数据类型转换(自动转换和强制转换)

    数据类型的转换,分为自动转换和强制转换.自动转换是程序在执行过程中“悄然”进行的转换,不需要用户提前声明,一般是从位数低的类型向位数高的类型转换;强制类型转换则必须在代码中声明,转换顺序不受限制. 自 ...

  9. Egret微端 创建项目(一)

    开发环境: window7 egret engine:5.0.14 egret wing:4.1.0 微端:v0.0.14 官方教程:http://developer.egret.com/cn/git ...

  10. 旋转数组的最小数字(C++ 和 Python 实现)

    (说明:本博客中的题目.题目详细说明及参考代码均摘自 “何海涛<剑指Offer:名企面试官精讲典型编程题>2012年”) 题目 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的 ...