Java Springboot 后端使用Mockito库进行单元测试流程分析

 更新时间:2024年10月15日 11:27:52   作者:Bling_  
使用Mock进行单元测试可以避免启动整个Spring框架,节省时间并降低外部依赖影响,Mock允许模拟外部方法和类,专注于测试方法的功能逻辑,本文给大家介绍Java Springboot 后端使用Mockito库进行单元测试流程分析,感兴趣的朋友跟随小编一起看看吧

1 为什么要使用mock进行单元测试

如果使用SpringbootTest进行单元测试,那么将启动spring服务,生成容器和bean并加载所有外部资源或依赖,当工程量很大时启动非常费时,显然为了一个小小的单元测试启动整个项目有一些大动干戈。

此外,有时部分外部依赖无法获取(如数据库连接、缓存或接口获取有问题),而单元测试旨在测试某个类具体的方法,测试的是该方法的功能逻辑,不应关注其依赖的对象,以防测试时各级方法互相调用太深(调用的方法应该在它自己的单元测试中进行测试),相当于只关注测试方法的宽度而不在意它的深度。

因此,对于要测试的方法中使用到的外部类或方法,可以考虑使用模拟对象来模拟它们的调用并自定义好返回的值,这样能够在不执行真实调用方法的同时让单元测试正常进行,这就是mock的原理。使用mock可以很好的将要测试的方法和它的依赖分隔开,降低了模块间的耦合,非常适合外部资源较难构造或方法调用太深的场景。

2 使用mock的注意点

  • 通过mock进行单元测试时并未启动spring,那么不会加载容器并生成bean,就无法通过自动注入的方式获取依赖,需要使用mock注解
  • 如果测试的相关功能与spring有关(比如AOP),那么还是需要使用SpringbootTest
  • 启用mock时可以看成启动了普通的java项目,在普通项目中能运行的代码在mock中也能运行
  • 单元测试时并非所有调用的外部方法或使用到的外部类都需要mock,如实体类可以直接new,工具类方法可以直接运行(不存在外部依赖的工具类,比如用来进行数据格式处理的工具类,通常调用其静态方法),不需要使用mock

3 mock使用流程

3.1 测试前配置

使用mock首先需要在pom文件中导入相关依赖,然后可以使用@Mock或Mockito库的mock方法来生成模拟对象,如果使用@Mock则需要先添加以下注解代码:

  • 在测试类前添加@RunWith(MockitoJUnitRunner.class)@ExtendWith(MockitoExtension.class)
  • 在测试类内添加如下方法:
//测试前准备,可以在这里进行一些基础配置
@BeforeEach
    void setUp(){
        MockitoAnnotations.openMocks(this); //开启mock,搭配@Mock使用
}

3.2 注入待测试类并模拟其中使用的变量

若需要测试的类为UserService,那么在它的测试类UserServiceTest中需要使用注解@InjectMocks注入UserService的实例,该注解的作用是自动创建并注入一个类的实例,同时将标记为@Mock的依赖注入到该实例中,注意@InjectMocks只能注入class。

@InjectMocks
UserService userService;

3.2.1 模拟成员变量

待测试类的成员变量需要使用@Mock注解注入,无法通过Mockito.mock()方法生成模拟对象,mock()方法生成的成员模拟对象在执行时会报空指针错误,并未真正生成模拟对象。
UserService中有成员变量UserMapper,那么使用如下代码注入:

@Mock
UserMapper

简单来说就是将UserService里面的所有成员变量直接复制到Test里,然后将@Autowired改成@Mock即可。

3.2.2 模拟静态对象

当代码中需要调用某个类的静态方法时,需要使用mockStatic()方法生成一个模拟静态对象,如被测方法中若存在如下代码:

DictUtil.getIns("A_END"); //DictUtil类中存在一些外部依赖,因此需要模拟

那么首先需要生成DictUtil的模拟静态对象:

mockStatic(DictUtil.class);  //上下两种写法都可以
MockedStatic<DictUtil> dictUtilMockedStatic = mockStatic(DictUtil.class);

3.2.3 模拟普通变量

对于代码中生成的一些中间变量,而在后期又需要使用这些变量进行方法调用时,需要先使用mock将中间变量模拟出,如被测方法中若存在代码:

UserCache.getInstance().getName("12345");

那么需要先模拟出UserCache的一个实例(打桩时不能对连续调用的方法同时打桩,需先生成中间变量),此时可以使用@Mock或者mock方法:

// 1. 当成测试类成员变量注入
@Mock
UserCache userCache;
// 2. 在测试方法中直接模拟
UserCache userCache = Mockito.mock(UserCache.class);

3.3 打桩模拟方法调用行为

3.3.1 非静态方法打桩

打桩即对调用的方法模拟传参并设置返回值,参数个数和返回值需要与原方法对应。打桩之后不会真正去调用,会忽略真实方法的执行结果,一般使用Mockito.when().thenReturn()进行打桩。
若被测类方法中存在代码:

User user = UserMapper.selectById("0000");

那么测试方法中的打桩可以是:

//实体类不需要mock,可以直接生成,然后使用setter方法赋值
User user = new User();
// 1. 固定了打桩参数为“0000”,源代码调用中参数为非“0000”时打桩失败
Mockito.when(UserMapper.selectById("0000")).thenReturn(user)
// 2. 为了代码的健壮性,通常使用Mockito.anyxxx()方法来接收参数,根据xxx的不同来指定不同的类型,只要是该类型的任意参数都可以打桩
Mockito.when(UserMapper.selectById(Mockito.anyString())).thenReturn(user)

3.3.2 静态方法打桩

以原代码UserCache.getInstance().getName("12345");为例,静态方法打桩有两种写法:
1) 普通写法

UserCache userCache = Mockito.mock(UserCache.class);
mockStatic(UserCache.class);  
Mockito.when(UserCache.getInstance()).thenReturn(userCache);
Mockito.when(userCache.getName(Mockito.anyString())).thenReturn("TestName");

2) lambda写法-无参静态方法调用

UserCache userCache = Mockito.mock(UserCache.class);
MockedStatic<UserCache> userCacheMockedStatic = mockStatic(UserCache.class);
//1. 当静态方法无参时,可以使用::进行方法调用
userCacheMockedStatic.when(UserCache::getInstance).thenReturn(userCache);
//2. 也可以使用lambda写法,使用.进行调用
userCacheMockedStatic.when(() -> UserCache.getInstance()).thenReturn(userCache);
//3. 不能使用如下写法:
//userCacheMockedStatic.when(UserCache.getInstance()).thenReturn(userCache);
Mockito.when(userCache.getName(Mockito.anyString())).thenReturn("TestName");

3) lambda写法-有参静态方法调用
假如上述getInstance方法有一个int参数,那么可以通过1)中的普通写法进行打桩,或者使用2)-2的方式进行打桩,然后使用Mockito.anyString()传参,但是不能使用2)-1或2)-3的写法。

3.3.3 Maven test静态模拟报错问题

当单独运行一个Test时没有问题,但是当集体执行Test时会出现如下报错:

‘static mocking is already registered in the current thread To create a new mock, the existing static mock registration must be deregistered’

这是因为有两个以上的测试方法都使用了同一个静态mock对象,如两个Test中都有MockedStatic<UserCache> userCacheMockedStatic = mockStatic(UserCache.class),而静态mock对象不允许公用,因此需要在每个静态mock对象执行后对其进行关闭,下一个方法才能继续使用它,故需要在每个Test末尾中写:userCacheMockedStatic.close()
由于MockedStatic<T>的父接口继承了AutoCloseable接口,因此可以使用 try-resources语句实现资源的自动关闭:

try(
	// mockStatic(UserCache.class);  //java8不支持这种写法
	MockedStatic<UserCache> userCacheMockedStatic = mockStatic(UserCache.class);
	// ……其他静态模拟对象声明
){
    //其他测试代码
}

也可以选择用try-catch-finally在finally语句里手动关闭:

try(
	MockedStatic<UserCache> userCacheMockedStatic = mockStatic(UserCache.class);
	// ……其他测试代码
)finally{
    userCacheMockedStatic.close();
}

3.4 断言判断测试结果

要检测测试的结果,需要使用断言Assertions进行判断,根据实际测试要求来:

User user = UserService.getById("123");
User expectUsr = new User("123");
Assertions.assertEquals(expectUsr ,user)://判断测试结果user与期望的对象是否一致
Assertions.assertNotNull(user)://判断测试结果user是否非空

到此这篇关于Java Springboot 后端使用Mockito库进行单元测试流程的文章就介绍到这了,更多相关Springboot Mockito单元测试内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java正则表达式精确查找和替换指定字符代码示例

    java正则表达式精确查找和替换指定字符代码示例

    这篇文章主要给大家介绍了关于java正则表达式精确查找和替换指定字符的相关资料,java正则表达式是一种用于匹配、查找和替换文本的强大工具,它可以用于验证输入是否符合特定的格式、从文本中提取信息、以及将文本中的某些内容替换成其他内容,需要的朋友可以参考下
    2024-04-04
  • SpringBoot实现多数据源的切换实践

    SpringBoot实现多数据源的切换实践

    这篇主要介绍了SpringBoot实现多数据源的切换,本文基于AOP来实现数据源的切换,文中通过示例代码介绍的非常详细,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • SpringBoot整合spring-retry实现接口请求重试机制及注意事项

    SpringBoot整合spring-retry实现接口请求重试机制及注意事项

    今天通过本文给大家介绍我们应该如何使用SpringBoot来整合spring-retry组件实现重试机制及注意事项,本文通过实例代码给大家介绍的非常详细,需要的朋友参考下吧
    2021-08-08
  • 使用Feign传递请求头信息(Finchley版本)

    使用Feign传递请求头信息(Finchley版本)

    这篇文章主要介绍了使用Feign传递请求头信息(Finchley版本),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • 解决SpringMVC同时接收Json和Restful时Request里有Map的问题

    解决SpringMVC同时接收Json和Restful时Request里有Map的问题

    今天小编就为大家分享一篇解决SpringMVC同时接收Json和Restful时Request里有Map的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-08-08
  • Java foreach相关原理及用法解析

    Java foreach相关原理及用法解析

    这篇文章主要介绍了Java foreach相关原理及用法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-07-07
  • Java学生信息类继承与接口的原理及使用方式

    Java学生信息类继承与接口的原理及使用方式

    这篇文章主要介绍了Java学生信息类继承与接口的原理及使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-02-02
  • Spring Bean配置方式总结

    Spring Bean配置方式总结

    定义Spring Bcan的3种方式分别是:基于XML 的方式配置、基于注解扫播方式配置、基于元数据类的配置,本文就通过代码示例给大家详细讲讲这三种配置方式,需要的朋友可以参考下
    2023-12-12
  • 为什么ConcurrentHashMap的key value不能为null,map可以?

    为什么ConcurrentHashMap的key value不能为null,map可以?

    这篇文章主要介绍了为什么ConcurrentHashMap的key value不能为null,map可以呢?具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01
  • java开发中的误区和细节整理

    java开发中的误区和细节整理

    这篇文章给大家整理了关于JAVA开发中的细节以及经常进入的误区整理,希望我们整理的内容能够给大家提供到帮助。
    2018-04-04

最新评论