0


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

则需要先添加以下注解代码:

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

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

若需要测试的类为

UserService

,那么在它的测试类

UserServiceTest

中需要使用注解

@InjectMocks

注入

UserService

的实例,该注解的作用是自动创建并注入一个类的实例,同时将标记为

@Mock

的依赖注入到该实例中,注意

@InjectMocks

只能注入class。

@InjectMocksUserService userService;

3.2.1 模拟成员变量

待测试类的**成员变量需要使用

@Mock

注解注入**,无法通过

Mockito.mock()

方法生成模拟对象,mock()方法生成的成员模拟对象在执行时会报空指针错误,并未真正生成模拟对象。

UserService

中有成员变量

UserMapper

,那么使用如下代码注入:

@MockUserMapper

简单来说就是将

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. 当成测试类成员变量注入@MockUserCache 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 =newUser();// 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 =newUser("123");
Assertions.assertEquals(expectUsr ,user)://判断测试结果user与期望的对象是否一致Assertions.assertNotNull(user)://判断测试结果user是否非空

本文转载自: https://blog.csdn.net/weixin_43281875/article/details/142854226
版权归原作者 Bling_ 所有, 如有侵权,请联系我们删除。

“Java Springboot 后端使用Mockito库进行单元测试流程”的评论:

还没有评论