Java mock测试框架之Mockito

参考

正文

在做单元测试的时候,有的时候用到的一些类,我们构造起来不是那么容易,比如HttpRequest,或者说某个Service依赖到了某个Dao,想构造service还得先构造dao,这些外部对象构造起来比较麻烦。 所以出现了Mock! 我们可以用 Mock 工具来模拟这些外部对象,来完成我们的单元测试。

实现Mock技术的优秀开源框架有很多,这里用几个简单例子来介绍一下Mockito的基本使用。

Mockito的引入

使用maven可以如下引入:

<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-all</artifactId>
  <version>1.9.5</version>
  <scope>test</scope>
</dependency>

Mockito使用步骤

使用mockito大致可以划分为以下几个步骤:

  1. 使用 mockito 生成 Mock 对象;
  2. 定义(并非录制) Mock 对象的行为和输出(expectations部分);
  3. 调用 Mock 对象方法进行单元测试;
  4. 对 Mock 对象的行为进行验证。

Mockito的第一个示例

@Test  
public void simpleTest(){  
    //创建mock对象,参数可以是类,也可以是接口  
    List<String> list = Mockito.mock(List.class);  
      
    //设置方法的预期返回值 (如果list.get(0) 被调用 ,调用之后返回 helloworld)
    //当然前提是要创建了Mock对象,如这里就是创建了跟 List相关的 Mock对象
    //这里还看不出什么作用,因为Mock 还看不出来,List很容易就能创建
    //假如是一个很复杂的对象,构造这样一个对象很有难度,使用Mock就很方便了,我们不用去一步一步填充它的属性去构造,
    //只需要Mock 一下,就可以拿到这个对象,去测试它的方法,(当然,如果方法有参数我们是需要传递的,像get(0))
    Mockito.when(list.get(0)).thenReturn("helloworld");  
    //list.get(0)的调用就会返回 helloworld
    String result = list.get(0);  
    System.out.println(result);
    
    //验证方法调用(是否调用了get(0))  
    Mockito.verify(list).get(0); 
    
    Assert.assertEquals("helloworld", result); 
}
    
@Test
public void testMockMethod() {
    Class1Mocked obj=mock(Class1Mocked.class); // 1. 使用 mockito 生成 Mock 对象

    when(obj.hello("z3")).thenReturn("hello l4"); // 2. 定义(并非录制) Mock 对象的行为和输出(expectations部分)

    String actual=obj.hello("z3"); // 3. 调用 Mock 对象方法进行单元测试;
    assertEquals("hello l4",actual);
    
    verify(obj).hello("z3"); // 4. 对 Mock 对象的行为进行验证。
    //verify(obj,times(1)).hello("z3"); //验证阶段可以通过增加参数(time(int)、atLeastOnce()、atLeast(int)、never()等)来精确验证调用次数。
}

验证顺序

@Test
public void testMockMethodInOrder() {
    Class1Mocked objOther = mock(Class1Mocked.class);
    Class1Mocked objCn = mock(Class1Mocked.class);

    when(objOther.hello("z3")).thenReturn("hello l4");
    when(objCn.hello("z3")).thenReturn("hello 张三");

    String other = objOther.hello("z3");
    assertEquals("hello l4", other);
    String cn = objCn.hello("z3");
    assertEquals("hello 张三", cn);

    InOrder inOrder = inOrder(objOther, objCn); //此行并不决定顺序,下面的两行才开始验证顺序
    inOrder.verify(objOther).hello("z3");
    inOrder.verify(objCn).hello("z3");
}

非局部模拟与局部模拟

在之前的介绍的模拟操作中,我们总是去模拟一整个类或者对象,对于没有使用 When().thenReturn()方法指定的函数,系统会返回各种类型的默认值(具体值可参考官方文档)。

而局部模拟创建出来的模拟对象依然是原系统对象,虽然可以使用方法When().thenReturn()来指定某些具体方法的返回值,但是没有被用此函数修改过的函数依然按照系统原始类的方式来执行。

非局部模拟
@Test
public void testSkipExpect() {
    Class1Mocked obj = mock(Class1Mocked.class);

    assertEquals(null, obj.hello("z3"));
    obj.show();

    verify(obj).hello("z3");
    verify(obj).show();
}

上面的代码省略了expectations部分(即定义代码行为和输出),运行该测试可以看到hello方法默认返回null(show方法本来就是无返回值的),而且在控制台中两个方法都没有输出任何语句。

局部模拟

mockito的局部模拟有两种方式,一种是doCallRealMethod()方式,另一种是spy()方式

局部模拟doCallRealMethod()
@Test
public void testCallRealMethod () {
    Class1Mocked obj = mock(Class1Mocked.class);

    doCallRealMethod().when(obj).hello("z3"); 
    
    assertEquals("hello z3",obj.hello("z3"));
    assertEquals(null,obj.hello("l4"));
    obj.show();

    verify(obj).hello("z3");
    verify(obj).hello("l4");
    verify(obj).show();
}

运行这个测试会发现在执行hello(“z3”)时会执行原有的代码,而执行hello(“l4”)时则是返回默认值null且没有输出打印,执行show()同样没有输出打印。

局部模拟spy()方式
@Test
public void testSpy() {
    Class1Mocked obj = spy(new Class1Mocked());
    
    doNothing().when(obj).show();
    
    assertEquals("hello z3",obj.hello("z3"));
    obj.show();
    
    verify(obj).hello("z3");
    verify(obj).show();
}

运行这个测试会发现在执行hello(“z3”)时会执行原有的代码,但是执行show()时在控制台中没有打印语句。

但值得注意的是在mockito的psy()方式模拟中expectations部分使用的语法不同,执行起来存在微妙的不同,如下:

值得注意的“陷阱”
@Test
public void testSpy2() {
    Class1Mocked obj = spy(new Class1Mocked());
    
    when(obj.hello("z3")).thenReturn("hello l4");
    
    assertEquals("hello l4",obj.hello("z3"));
    
    verify(obj).hello("z3");
}

上面的代码虽然能顺利运行,但在控制台中输出了hello z3,说明实际的代码仍然执行了,只是mockito在最后替换了返回值。

但下面的代码就不会执行实际的代码:

@Test
public void testSpy3() {
    Class1Mocked obj = spy(new Class1Mocked());
    
    doReturn("hello l4").when(obj).hello("z3");
    
    assertEquals("hello l4",obj.hello("z3"));
    
    verify(obj).hello("z3");
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值