快速入门JAVA单元测试——mock
Posted 水中加点糖
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了快速入门JAVA单元测试——mock相关的知识,希望对你有一定的参考价值。
背景
为了确保代码的质量,对编写的代码进行单元测试是非常有必要的。
在JAVA项目中,一般的项目结构比较复杂、依赖众多。在微服务与spring boot大行其道的今天,单纯靠junit来进行单元测试一般很难完成对模块的单元测试。
为了让JAVA项目中的单元测试更加灵活便于编写,各种mock框架应运而生,其中最为常用和经典的mock框架非mockito与powermock莫属。
为了快速入门,本文将通过几个实例让大家快速了解mokito与powermock的使用方法。
同时将对mokito与powermock中几个常见的疑问点通过几个实例来解答疑惑。
示例场景
在一个庞大的项目中,有一个service和一个dao,现在想在对它们进行单元测试,仅验证一下代码中的业务逻辑代码是否正确。
public interface UserService {
String getUsernameById(int userId);
}
public class UserServiceImpl implements UserService {
@Resource
private UserDao userDao;
@Override
public String getUsernameById(int userId) {
return userDao.getUsernameById(userId);
}
}
public interface UserDao {
String getUsernameById(int userId);
}
由于这两个类一在spring容器中,通常由于网络或环境原因,使用@SpringBootTest注解来将数据库或整个项目全准备好一般也不太方便,这里可能就需要mock来解决了。
@mock注解
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.MockitoAnnotations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class UserServiceTest {
@Mock
private UserService userService;
@Before
public void testInit() {
MockitoAnnotations.initMocks(this);
when(userService.getUsernameById(1)).thenReturn("1111111111");
}
@Test
public void getUsernameByIdTest() {
String username = userService.getUsernameById(1);
System.out.println(username);
}
}
输出:
1111111111
从上面的代码中可以看出,被@mock修饰后的变量userService则会产生一个userService的mock类,不用@mock注解用mock方法来产生一个userService是一样的效果
@InjectMocks注解
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.mockito.Mockito.when;
public class UserServiceTest {
@Mock
private UserDao userDao;
@InjectMocks
private UserServiceImpl userService;
@Before
public void testInit() {
MockitoAnnotations.initMocks(this);
when(userDao.getUsernameById(2)).thenReturn("2222222222");
}
@Test
public void getUsernameByIdTest() {
String username = userService.getUsernameById(2);
System.out.println(username);
}
}
输出:
2222222222
@InjectMocks注解修饰的类不能修饰抽象或接口类,被它修饰后,其被mock后的成员变量会注入到@InjectMocks的类中
比如上面的userDao则会被注入到userService中,当调用userService.getUsernameById方法时,原代码里的userDao.getUsernameById方法则会执行mock方法
spy
@Test
public void spyTest() {
List<String> spyList = Mockito.spy(new ArrayList<>());
when(spyList.size()).thenReturn(1000);
spyList.add("abc");
spyList.add("def");
System.out.println(spyList.get(0));
System.out.println(spyList.size());
}
输出:
abc
1000
被spy修饰的方法默认会调用其真实的方法,如果有被mock限定了的条件才会先执行mock方法。
比如上面的例子,spyList这个list是被spy方法mock出来的,由于在mock声明时只mock了它的size方法,所以调用它的get方法时还会执行原来的逻辑
静态方法mock
有时在单元测试中会遇到需要对静态方法也进行mock,比如想要对下面这个IpUtils工具类进行mock,那么mock注解就无能为力了。
import java.net.InetAddress;
import java.net.UnknownHostException;
public class IpUtils {
public static String getIp() {
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
return null;
}
}
mock静态方法
一般对于需要将静态方法进行mock可以借助于powermock来实现。
示例代码如下:
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.mockito.Mockito.when;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
@RunWith(PowerMockRunner.class)
@PrepareForTest(IpUtils.class)
public class UserServiceTest {
@Before
public void init() {
mockStatic(IpUtils.class);
when(IpUtils.getIp()).thenReturn("192.168.1.123");
}
@Test
public void testIp() {
System.out.println(IpUtils.getIp());
}
}
输出:
192.168.1.123
使用时需要先用@PrepareForTest注解将需要mock的静态类定义,之后再调用mockStatic方法进行mock即可。
上面的代码测试时的依赖如下:
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>2.0.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>2.0.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.23.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.7.4</version>
<scope>test</scope>
</dependency>
对桩模块与驱动模块概念的理解
在有了上面的示例代码体验后,再来理解一下什么是桩模块,什么是驱动模块
先看下百度百科上对桩模块的解释:
桩模块(Stub)是指模拟被测试的模块所调用的模块,而不是软件产品的组成的部分。主模块作为驱动模块,与之直接相连的模块用桩模块代替。在集成测试前要为被测模块编制一些模拟其下级模块功能的“替身”模块,以代替被测模块的接口,接受或传递被测模块的数据,这些专供测试用的“假”模块称为被测模块的桩模块。
那么根据上面的定义,将桩模块和驱动模块与@Mock注解和@InjectMocks进行对应,那么被@Mock修饰的模块则是桩模块,被@InjectMocks修饰的模块则是驱动模块
以上是关于快速入门JAVA单元测试——mock的主要内容,如果未能解决你的问题,请参考以下文章
springboot2.0入门----mock模拟测试+单元测试