轻松上手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修饰的模块则是驱动模块
推荐阅读
-
轻松上手!Android开发者快速切换到Kotlin的指南
-
轻松入门:Junit在Java单元测试中的详细指南
-
实战指南:轻松掌握Java IDEA和JUnit的单元测试
-
轻松上手Java单元测试:mock的快速指南
-
轻松上手!Java单元测试中的Mockito框架详解
-
轻松上手!Java单元测试框架(Mockito)的快速指南
-
轻松掌握S7-200 PLC的PID指令:图解指南让你快速上手OUT操作
-
快速上手!3分钟轻松完成Spring Boot不同环境的配置实战指南
-
如何轻松快速上手Java编程?初学者高效学习指南
-
【2022新手指南】Java编程进阶之路 - 六、技术架构篇 ### MySQL索引底层解析与优化实战 - 你会讲解MySQL索引的数据结构吗?性能调优技巧知多少? - Redis深度揭秘:你知道多少?从基础到哨兵、主从复制全梳理 - Redis持久化及哨兵模式详解,还有集群搭建和Leader选举黑箱打开 - Zookeeper是个啥?特性和应用场景大公开 - ZooKeeper集群搭建攻略及 Leader选举、读写一致性、共享锁实现细节 - 探究ZooKeeper中的Leader选举机制及其在分布式环境中的作用 - Zab协议深入剖析:原理、功能与在Zookeeper中的核心地位 - RabbitMQ全方位解读:工作模式、消费限流、可靠投递与配置策略 - 设计者视角:RabbitMQ过期时间、死信队列与延时队列实践指南 - RocketMQ特性和应用场景揭示:理解其精髓与差异化优势 - Kafka详细介绍:特性及广泛应用于实时数据处理的场景解析 - ElasticSearch实力揭秘:特性概述与作为搜索引擎的广泛应用 - MongoDB认知升级:非关系型数据库的优势阐述,安装与使用实战教学 - BIO/NIO/AIO网络模型对比:掌握它们的区别与在网络编程中的实际应用 - Netty带你飞:理解其超快速度背后的秘密,包括线程模型分析 - 网络通信黑科技:Netty编解码原理与常用编解码器的应用,Protostuff实战演示 - 解密Netty粘包与拆包现象,怎样有效应对这一常见问题 - 自定义Netty心跳检测机制,轻松调整检测间隔时间的艺术 - Dubbo轻骑兵介绍:核心特性概览,服务降级实战与其实现益处 - Dubbo三大神器解读:本地存根与本地伪装的实战运用与优势呈现 ----------------------- 七、结语与回顾