Jmockit可以做什么

使用JMockit API来mock被依赖的代码,从而进行隔离测试。

  • 类级别整体mock和部分方法重写
  • 实例级别整体mock和部分mock
  • mock静态方法、私有变量、局部方法
  • 灵活的参数匹配

maven依赖

Jmockit可以和junit和TestNG配合使用。需要注意的是:

  • 如果使用Junit4.5以上,jmockit依赖需要在junit4之前;或者在测试类上添加注解 @RunWith(JMockit.class)。
  • 如果是TestNG 6.2+ 或者 JUnit 5+, 没有位置限制
		<!-- 如果使用Junit4.5以上 jmockit依赖需要在junit4之前 -->
<!-- 或者在测试类上添加注解 @RunWith(JMockit.class) -->
<!-- 如果是TestNG 6.2+ 或者 JUnit 5+, 没有位置限制 -->
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>1.30</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>

版本记录 版本更新较快。

使用

涉及到三个类:

  • 测试类:执行测试代码的类。
  • CUT(Code Under Test):被测试的类,测试此类是否能正确地工作。
  • 依赖类:CUT会调用依赖类的方法。

CUT

@Data
public class Person { private String name;
private Integer age;
private Person friend; public Person(){} public Person(String name, Integer age, Person friend){
this.age = age;
this.name = name;
this.friend = friend;
} @Override
public boolean equals(Object obj) {
return this.name.equals(((Person)obj).getName());
}
} @Service
public class PersonService { public String showName(String name){
System.out.println("person show name : " + name);
return name;
} public int showAge(int age) {
System.out.println("person show age : " + age);
return age;
} public Person getDefaultPerson(){
return new Person("miao", 3, null);
}
} @Service
public class CoderService {
@Value("${coder.service.desc}")
private String desc; public String showWork(String work){
return work;
} public int showSalary(int salary){
return salary;
} public String getDesc() {
return desc;
} public String getPersonName(Person person){
return person.getName();
}
}

基本流程

record(录制)---- replay(回放) ---- verify(验证)

record : 设置将要被调用的方法和返回值。

  • Expections中的方法至少被调用一次,否则会出现missing invocation错误。调用次数和调用顺序不限。
  • StrictExpectations中方法调用的次数和顺序都必须严格执行。如果出现了在StrictExpectations中没有声明的方法,会出现unexpected invocation错误。

replay:调用(未被)录制的方法,被录制的方法调用会被JMockit拦截并重定向到record阶段设定的行为。

verify:基于行为的验证,测试CUT是否正确调用了依赖类,包括:调用了哪些方法;通过怎样的参数;调用了多少次;调用的相对顺序(VerificationsInOrder)等。可以使用times,minTimes,maxTimes来验证。

    @Test
public void mockProcessTest(final @Mocked PersonService target){
//录制预期行为
new Expectations(){
{
target.showName(anyString);
result = "test1";
target.showAge(anyInt);
result = -1;
}
}; //测试代码
Assert.assertTrue("test1".equals(target.showName("test2")));
Assert.assertTrue(-1 == target.showAge(12));
Assert.assertTrue(-1 == target.showAge(12)); //验证
new Verifications(){
{
target.showName("test1");
times = 0; //执行了0次。参数一致的才会计数
target.showAge(12);
times = 2; //执行了2次
}
};
} /**
* Expections中的方法至少被调用一次,否则会出现missing invocation错误.
* 调用次数和调用顺序不限.
*/
@Test
public void mockExpectationsProcessTest(final @Mocked PersonService service){
new Expectations(){{
service.showAge(anyInt);
result = -1;
}};
//只调用showName会报错 Missing 1 invocation
service.showName("hahah");
service.showAge(12);
}
/**
* StrictExpectations中方法调用的次数和顺序都必须严格执行。如果出现了在StrictExpectations中没有声明的方法,会出现unexpected invocation错误。
* 没有必要做Verifications验证。
*/
@Test
public void mockStrictExpectationsProcessTest(final @Mocked PersonService service){
new StrictExpectations(){{
service.showAge(anyInt);
result = -1;
service.showName(anyString);
result = "ok";
}}; //1.下面只执行了一个录制方法,报错:unexpected invocation, Missing invocation
// Assert.assertTrue(-1 == service.showAge(12)); //2.下面与录制顺序不一致,会报错:unexpected invocation, Missing invocation
// Assert.assertTrue("ok".equals(service.showName("test")));
// Assert.assertTrue(-1 == service.showAge(12)); //3.调用没有录制的方法,报错 Unexpected invocation
// service.getDefaultPerson(); //必须全部执行录制的方法,且顺序一致
Assert.assertTrue(-1 == service.showAge(12));
Assert.assertTrue("ok".equals(service.showName("test")));
}

部分注解说明

RunWith(JMockit.class): 指定单元测试的执行类为JMockit.class。

Tested: 指定被测试类,同时mock实例并注入测试类;依赖的类使用Injectable注入。

Injectable: 将对象进行mock并注入测试类。

Mocked:mock一种类型,并注入测试类。

Mocked与Injectable区别:

  • Mocked 注入的依赖,类的所有实例都被mock,record的方法,在replay时,按照record的结果返回;没有record的方法返回默认值。
  • Injectable 注入的依赖,只mock指定的实例,record的方法,在replay时,按照record的结果返回;没有record的方法返回默认值。没有mock的实例,调用其原始方法。
@RunWith(JMockit.class)
public class MockTest { //@Mocked 修饰,所有实例都会被mock
@Mocked
private PersonService personService; // @Injectable 修饰,只mock指定的实例。
@Injectable
private CoderService coderService; @Test
public void testInstance(){
new Expectations(){
{
personService.showAge(anyInt);
result = -1; personService.getDefaultPerson();
result = new Person("me", 4, null); Deencapsulation.invoke(coderService, "showWork", anyString);
result = "java"; }
}; //record的方法,按照给定的结果返回
Assert.assertTrue(-1 == personService.showAge(11));
Assert.assertTrue("java".equals(coderService.showWork("nothing")));
Assert.assertTrue(4 == personService.getDefaultPerson().getAge());
//没有录制的方法,返回默认值
Assert.assertTrue(personService.showName("testName") == null);
Assert.assertTrue(coderService.showSalary(100) == 0); //Mock 所有PersonServiceImpl实例
PersonService pservice = new PersonService();
Assert.assertTrue(-1 == pservice.showAge(11));
Assert.assertTrue(pservice.showName("testName") == null); //新生成的CoderService实例没有被mock
CoderService cservice = new CoderService();
Assert.assertTrue("something".equals(cservice.showWork("something")));
Assert.assertTrue(cservice.showSalary(100) == 100);
} /**
* 可以将参数注入,与类中注入结果一致。
* 但是不要同时在参数中注入,且在测试类中注入,会影响执行结果。
*/
@Test
public void testInjectObj(final @Injectable CoderService coderService){
new Expectations(){
{
coderService.showWork(anyString);
result = "ok";
}
};
Assert.assertTrue("ok".equals(coderService.showWork("hello")));
Assert.assertTrue(coderService.showSalary(100) == 0);
}

使用示例

  • 部分mock(实例级别)

    在Expectations中传入被mock实例。 则replay的方法在Expectations中被录制时,按照record结果返回;没有被录制,则调用原有代码。

    与之对应的是Injectable 注入的实例,record的方法,在replay时按照record结果返回;没有record的方法,返回默认值。
    /**
* 部分mock,在Expectations中传入被mock实例。
* replay的方法在Expectations中被录制时,按照record结果返回;
* 没有被录制,则调用原有代码
*/
@Test
public void partiallyMock(){
new Expectations(personService){
{
personService.showAge(anyInt);
result = -1;
}
};
//被录制的方法,按照record结果返回
Assert.assertTrue(-1 == personService.showAge(11));
//未录制的方法,调用原有代码
Assert.assertTrue("testName".equals(personService.showName("testName")));
}
  • mockUp(类级别)

    mockUp的类,被mock的方法,replay的时候都执行mock的方法;没有被mock的方法,调用原有代码。

    与之对应的事Mocked注入的类,所有record的方法按照record结果返回;没有record的方法,返回默认值。
    /**
* mockUp类,被mock的方法,replay的时候都执行mock的方法;
* 没有被mock的方法,调用原有代码
*/
@Test
public void mockUpTest(){ new MockUp<PersonService>(){
@Mock
public String showName(String name){
return "mocked";
}
}; Assert.assertTrue("mocked".equals(new PersonService().showName("test")));
Assert.assertTrue(1 == new PersonService().showAge(1));
}
  • mock 静态方法
    @Test
public void testStaticMethod(){
new Expectations(CollectionUtils.class){{
CollectionUtils.isEmpty((Collection<?>) any);
result = true;
}};
List<Integer> list = Lists.newArrayList(1,2,3);
Assert.assertTrue(list.size() == 3);
Assert.assertTrue(CollectionUtils.isEmpty(list));
}
  • mock 私有变量
  • 局部方法
  • mock 参数匹配问题

    参数为基本类型时,若mock方法参数设置为anyXXX,则任意此类型参数都可mock成功;若mock方法参数为具体值,则实际参数 equals mock参数时,才能mock成功。

    参数为非基本类型时,mock参数不可以为any,执行报错;若mock参数为具体值,只有传递的参数 equals mock参数时,才能mock成功。
public class CoderServiceTest {

    @Tested
private CoderService coderService;
@Injectable
private PersonService personService; @Test
public void testMockCase(){
new Expectations(coderService){{
//mock私有变量
Deencapsulation.setField(coderService, "desc", "coderDesc");
//mock 方法
Deencapsulation.invoke(coderService, "showWork", anyString);
result = "noWork";
}}; //mock 私有变量成功
Assert.assertTrue(coderService.getDesc().equals("coderDesc"));
//mock 私有方法
Assert.assertTrue(coderService.showWork("coder").equals("noWork"));
} @Test
public void testParamCase(){
new Expectations(coderService){{
//基本类型,mock参数为anyXXX
Deencapsulation.invoke(coderService, "showWork", anyString);
result = "mocked"; //基本类型,mock参数为实际值
Deencapsulation.invoke(coderService, "showSalary", 12);
result = -1; //非基本类型,mock参数不可以为anyXXX,会报错 java.lang.IllegalArgumentException: Invalid null value passed as argument 0
// Deencapsulation.invoke(coderService, "getPersonName", (Person)any);
// result = "mocked"; //基本类型,mock参数为实际值
Deencapsulation.invoke(coderService, "getPersonName", new Person("me", 3, null));
result = "mocked";
}}; //基本类型,mock参数为anyXXX, 实际参数为任意值mock成功
Assert.assertTrue(coderService.showWork("java").equals("mocked")); //基本类型,mock参数为具体值, 实际参数 equals mock参数时,mock成功
Assert.assertTrue(coderService.showSalary(12) == -1);
Assert.assertTrue(coderService.showSalary(100) == 100); //基本类型,mock参数为实际值,实际参数 equals mock参数时,mock成功
Assert.assertTrue("mocked".equals(coderService.getPersonName(new Person("me", 4, null))));
Assert.assertFalse("mocked".equals(coderService.getPersonName(new Person("you", 3, null))));
} }

注意事项

  • Tested指定的被测试类,必须是实现类,而非接口。否则不能正确实例化,报错NPE。

参考

JMockit git book 很挫!!!

JMockit Tutorial 很详细,部分示例已过时,推荐指数四颗星

官网 英文版

JMockit mock 实例使用的是asm技术

JMockit使用总结的更多相关文章

  1. JMockit

    [TOC] 简介 JMockit是基于JavaSE5中的java.lang.instrument包开发,内部使用ASM库来动态修改java的字节码,使得java这种静态语言可以想动态脚本语言一样动态设 ...

  2. Jmockit使用

    引用单元测试中mock的使用及mock神器jmockit实践中的java单元测试中各种Mock框架对比,就能明白JMockit有多么强大: JMockit是基于JavaSE5中的java.lang.i ...

  3. 使用JUnit4与JMockit进行打桩测试

    1. 何为Mock 项目中各个模块,各个类之间会有互相依赖的关系,在单元测试中,我们只关心被测试的单元,对于其依赖的单元并不关心(会有另外针对该单元的测试). 比如,逻辑层A类依赖了数据访问层B类的取 ...

  4. jmockit学习

    下图为jmockit 类图.在我们编写代码时几乎都会用到Expectations(期望)和Verifications(校验),二者均继承自Invacations. 常会用到的注解有:@Mocked @ ...

  5. jmockit学习总结

    mock类型和实例 从依赖的测试代码调用的方法和构造函数是mock(模拟)的目标. Mocking提供了我们需要的机制,以便将被测试的代码与(一些)依赖关系隔离开来.我们通过声明适当的模拟字段和/或模 ...

  6. Jmockit之mock特性详解

    本文是Jmockit学习过程中,根据官网所列的工具特性进行解读. 1.调用次数约束(Invocation count constraints) 可以通过调用计数约束来指定预期和/或允许匹配给定期望的调 ...

  7. 单元测试系列:Mock工具Jmockit使用介绍

    更多原创测试技术文章同步更新到微信公众号 :三国测,敬请扫码关注个人的微信号,感谢! 原文链接:http://www.cnblogs.com/zishi/p/6760272.html Mock工具Jm ...

  8. JMockit常用操作

    JMockit常用操作 2017-11-30 转自:http://blog.csdn.net/foreverling/article/details/51234149 目录 1 基本概念  1.1 常 ...

  9. 单元测试系列之十一:Jmockit之mock特性详解

    本文是Jmockit学习过程中,根据官网所列的工具特性进行解读. 1.调用次数约束(Invocation count constraints) 可以通过调用计数约束来指定预期和/或允许匹配给定期望的调 ...

随机推荐

  1. LeetCode-Triangle[dp]

    Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent n ...

  2. Linux安装搜狗输入法教程

    最近开始学习linux 在安装输入法中遇到的一些问题,最终成功安装,也得益于网络上的前辈写的文章,现在将全部安装步骤以及遇到的一些问题总结如下:   基本上分三步走 1,添加fcitx的键盘输入法系统 ...

  3. Codeforces Round #427 (Div. 2)

    B. The number on the board 题意: 有一个数字,它的每个数位上的数字的和不小于等于k.现在他改变了若干位,变成了一个新的数n,问现在的数和原来的数最多有多少位不同. 思路: ...

  4. [Android]Android焦点流程代码分析

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/7286503.html 通过View的View::focusSe ...

  5. Hibernate与 MyBatis的比较(转)

    第一章     Hibernate与MyBatis Hibernate 是当前最流行的O/R mapping框架,它出身于sf.NET,现在已经成为Jboss的一部分. Mybatis 是另外一种优秀 ...

  6. git远程仓库之从远程库克隆

    上次我们讲了先有本地库,后有远程库的时候,如何关联远程库. 现在,假设我们从零开发,那么最好的方式是先创建远程库,然后,从远程库克隆. 首先,登陆GitHub,创建一个新的仓库,名字叫gitskill ...

  7. NYOJ--1236--挑战密室(第八届河南省程序设计大赛)

    挑战密室 时间限制:1000 ms  |  内存限制:65535 KB 难度:4   描述 R组织的特工Dr. Kong 为了寻找丢失的超体元素,不幸陷入WTO密室.Dr. Kong必须尽快找到解锁密 ...

  8. Spring 实现自定义 bean 的扩展

    Spring mvc 提供了扩展 xml 的机制,用来编写自定义的 xml bean ,例如 dubbo 框架,就利用这个机制实现了好多的 dubbo bean,比如 <dubbo:applic ...

  9. .net程序实现给机器加域,添加域账号到本地管理员

    以下.net代码中共有两个方法. AddComputerToDomain实现给把本计算机添加到某个域中 AddDomainUserToLocalAdminGroup实现把域中某用户添加到本地管理员 请 ...

  10. akoj-1280另类阶乘问题

    另类阶乘问题 Time Limit:3000MS  Memory Limit:65536K Total Submit:22 Accepted:20 Description 大家都知道阶乘这个概念,举个 ...