关于Mock、Spy、@MockBean、@SpyBean的笔记
前言
Mock是将目标对象整个模拟 ,所有方法默认都返回null,并且原方法中的代码逻辑不会执行,被Mock出来的对象,想用哪个方法,哪个方法就需要打桩,否则返回null;Spy可实现对目标对象部分方法、特定入参条件时的打桩,没有被打桩的方法,将会真实调用。
本文maven依赖
本文使用了
Spring、Junit5、Mokito、commons-lang3工具类。
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
1. 非Spring环境
假设我们有如下服务:
public class DemoService {
public String serviceA(String name) {
System.out.println("serviceA 被真实调用了,当前入参:" + name);
return "serviceA 真实返回:" + name;
}
public String serviceB(String name) {
System.out.println("serviceB 被真实调用了,当前入参:" + name);
return "serviceB 真实返回:" + name;
}
}
我们用如下单测代码示例,注释已经很清楚了,此处不再赘述。
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
class DemoServiceTest {
@Test
@DisplayName("Mock的正确姿势")
void testMock() {
// Mock 出一个目标对象的实例,注意不是 new 出来的真实对象
DemoService mockService = Mockito.mock(DemoService.class);
// 假设我们仅对 serviceA 方法,并且入参等于 "asdf" 时进行打桩
Mockito.when(mockService.serviceA(Mockito.eq("asdf")))
.thenReturn("mock:serviceA方法入参等于asdf时的特定返回值");
// 由于上边已经打桩了,并且这个调用正好命中打桩规则,因次返回值将是 "mock:serviceA方法入参等于asdf时的特定返回值"
String asdf = mockService.serviceA("asdf");
System.out.println("asdf = " + asdf);
Assertions.assertEquals("mock:serviceA方法入参等于asdf时的特定返回值", asdf);
// 虽然也是调用 serviceA 方法,但由于没有命中打桩规则,所以返回值是 null
String qwer = mockService.serviceA("qwer");
System.out.println("qwer = " + qwer);
Assertions.assertNull(qwer);
// serviceB 根本没有打桩,但由于 mockService 这个对象实例是 Mock 出来的,
// 所以 serviceB 的方法体代码不会被执行,并且返回值固定为 null,不管入参是什么
for (int i = 0; i < 3; i++) {
String returnValueFromRandom = mockService.serviceB(RandomStringUtils.random(10));
System.out.println("returnValueFromRandom 第[" + i + "]次 = " + returnValueFromRandom);
Assertions.assertNull(returnValueFromRandom);
}
}
@Test
@DisplayName("Spy的错误打桩姿势")
void testBadSpy() {
// Spy 一个目标对象的实例,注意不是 new 出来的 100% 真实的实例,也不是 100% 假的实例
// 到底有多真,有多假,取决于打桩埋点的覆盖程度
DemoService spyService = Mockito.spy(DemoService.class);
// 打桩错误示例:参照 Mock 打桩的写法,预期对 serviceA 方法打桩,
// 并且当入参等于 "asdf" 时,返回特定值 "spy:serviceA方法入参等于asdf时的特定返回值"
Mockito.when(spyService.serviceA(Mockito.eq("asdf")))
.thenReturn("spy:serviceA方法入参等于asdf时的特定返回值");
// 由于上边已经打桩了,并且这个调用正好命中打桩规则,因次返回值将是 "spy:serviceA方法入参等于asdf时的特定返回值"
String asdf = spyService.serviceA("asdf");
System.out.println("asdf = " + asdf);
Assertions.assertEquals("spy:serviceA方法入参等于asdf时的特定返回值", asdf);
// 注意:上述对入参等于 "asdf" 时,被"spy错误打桩"的方法 serviceA 的返回值的断言,是可以跑通的
// 但是,存在如下两个问题:
// 1. serviceA 被真实调用了;(这一点往往不是我们的预期)
// 2. serviceA 被真实调用时,它的实际入参,其实并不是 "asdf",而是 null。
// (想象一下如果 serviceA 方法体内真正运行时,极有可能由于实际入参是 null 而抛出异常中断执行,这是一种灾难)
// 下述调用,虽然 serviceA 被打桩,由于 "qwer" 并没有命中打桩点,所以它不会返回打桩点设定的返回值
// 不同于 Mock 出来的对象,下述调用会真实地执行 serviceA 方法体代码块,而不是像 Mock 一样返回 null
System.out.println("==============================================");
String qwer = spyService.serviceA("qwer");
System.out.println("qwer = " + qwer);
Assertions
