Mock框架:Mockito小叙

最近有很多需求都依赖其它服务,像dubbo,http接口都有,而本地开发的时候,没有合适的服务可以调用,因此借用简单的mock操作来做下单元测试,正好也归纳一下常规用法

先直接来个例子,字面意思看就行了,谁都能看懂

package com.lihuia.code.mock;

import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import java.util.List;

import static org.mockito.Mockito.when;

/**
* @author lihuia.com
* @date 2021-06-30 22:06:14
*/

public class MockTest extends AbstractTestNGSpringContextTests {

@Mock
private List<String> list;

@BeforeClass
public void beforeClass() {
MockitoAnnotations.initMocks(this);
}

@Test
public void mockTest() {
String s = "Hello";
when(list.get(Mockito.anyInt())).thenReturn(s);
Assert.assertEquals(list.get(0), s);
Assert.assertEquals(list.get(1), s);
Assert.assertEquals(list.get(2), s);
}
}

首先说明,测试方法里的几个断言都是通过的,正常情况下这个list没有做任何add操作,get方法不可能有值,更不可能每个值都是Hello,因此起作用的就是when,thenReturn这行代码,字面意思就是当调用list.get(任意index),返回值都会是Hello,这相当于强制约定了返回值

接着再反过来看整个流程

1、list添加了一个@Mock注解,说明list是Mock了一个List类型的proxy对象,拥有了List的方法和属性
2、MockitoAnnotations.initMocks(this)就是初始化mock对象,否则就是null,放在前置@BeforeClass或者构造方法里都可以
3、打桩,关键的when和thenReturn保存对象方法,输入和输出参数这种调用行为
4、list.get方法,调用的就是proxy对象的get方法,返回上面指定的输出结果

所以整个用法比较直接,maven依赖需要加上mockito-core配置,我这里添加了spring-boot-starter-test,里面已经包含了

NewImage

 

有了上面一个简单例子,基本玩法就很清楚了,Mock掉想mock的类,通过自定义接口方法的输入和输出,来满足我们的流程不受阻塞

下面这个例子可能更普遍一些,比如helloService是一个其它服务的dubbo接口,当前我的服务做单元测试,依赖这个dubbo接口,可是因为一些不可抗拒的原因导致调用不通,或者不稳定,或者别人根本就没开发完,这时候我们就可以自行mock这个接口和它的输入输出,以达到我们的单元测试可以顺利进行

这里说明一下区别,正常如果不做mock的话,我们要进行接口调用,可能就要添加@SpringbootApplication,或者更具体一点类似@ComponentScan来进行扫描,然后通过@Autowired或者@Resource来进行对象注入,进而进行方法调用,当然返回值都是真实服务接口的返回;而用mockito的话,@Mock声明了mock类型,initMocks或者RunWith注解来进行类型初始化,when和thenReturn相当于就是我们测试要构造的数据

NewImage

断言的报错在第二条,因为sayHello传入任何String都会返回Hello world

其实这样mock的好处是,可以随意构造接口返回值,而往往这些返回值会作为我们自己系统的输入,所以更有利于测试我们自己系统健壮性

 

上面都是@Mock的对象,还有一种类型,@Spy的对象,简单来说,@Mock的类所有方法都会mock,打桩的返回thenReturn,没打桩的返回null,@Spy的类when打桩了的方法会mock,其它方法都是真实方法调用

比如下面两种mock类对比

NewImage

1、打桩了sayHello方法,两种mock类,sayHello方法都返回mock的返回数据无误
2、接口的另一个getCurrentTime方法,@Mock类没打桩,因此返回null,@Spy类没打桩,会进行真实调用实现方法,返回当前时间
3、verify校验,getCurrentTime确实执行过,甚至还可以校验执行的次数

这里有一点,@Spy注解的必须有初始值才行

 

对于@Spy通过when,thenReturn打桩的对象,可以做下面调试,可以发现在执行打桩的时候,是真实执行到了具体方法中,但是断言的结果无误说明,本身方法返回的是Hello, lihui,但由于受限于打桩的条件,最终还是返回了Hello, world

UntitledImage

也就是说@Spy会调用真实的方法,而@Mock是不会进入到方法体执行的,这点是@Mock不同的

那假如我用@Spy也指向mock,并不想调用真实方法,可以将when,thenReturn改为doReturn,when

@Test
public void mockTest() {
String s = "Hello world";
//when(helloService.sayHello("lihui")).thenReturn(s);
doReturn(s).when(helloService).sayHello("lihui");
Assert.assertEquals(helloService.sayHello("lihui"), s);
verify(helloService, times(1)).sayHello("lihui");
}

这样,就不会执行具体方法体了,主要是为了避免执行的时候出现问题,比如抛出个异常啥的

 

依赖注入的问题,假如有一些@Mock,@Spy的依赖对象如要注入我的测试类里,可以用@InjectMocks注解

比如HelloService里,依赖timeService这个mock对象,@InjectMocks注解会自动依赖注入@Mock注解的timeService对象

UntitledImage

根据上面说的,@Mock的对象,无论通过什么打桩,都不会调用真实的方法体,因此这里的getCurrentTime()方法内部不会执行就直接返回2021了

 

还有一个打桩方式,doAnswer,when,大致意思和when,thenReturn一样的,当调用sayHello的时候,返回Hello lihui

@Test
public void mockTest() {
String s = "Hello lihui";
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
return s;
}
}).when(helloService).sayHello("world");

Assert.assertEquals(helloService.sayHello("world"), s);
}

看上去没啥特别的,但对于@Spy对象,它和doReturn一样,不会真实调用方法内部

doAnswer()返回的是一个Answer对象,需要执行任何操作,都可以在里面实现,比如随手写一个例子,构造一个对象,避免DAO层数据库操作

@Test
public void mockTest() {
String name = "lihui";
doAnswer(new Answer<UserDO>() {
@Override
public UserDO answer(InvocationOnMock invocationOnMock) throws Throwable {
UserDO userDO = new UserDO();
userDO.setName(name);
return userDO;
}
}).when(userDAO).getByName(name);

Assert.assertEquals(userDAO.getByName(name).getName(), name);
}

 

最后总结一下

1、@Mock是一个mock的对象,@Spy是一个真实的对象,它们都可以配置对象行为;@Mock没有指定的行为返回null,@Spy会调用真实对象的行为
2、when,thenReturn和doReturn,when对@Mock来说没区别,对@Spy来说,前者会先执行真实对象指定的行为,后者不会
3、@Mock和@Spy注解的对象属性,都会依赖注入到@InjectMocks注解的对象当中

OVER

发表评论