单元测试之_Mockio
前言
Github:https://github.com/HealerJean
1、MOCK
编程
单元测试中,一个重要原则就是不扩大测试范围,尽可能将
mock
外部依赖,例如外部的RPC
服务、数据库等中间件。被mock
的对象可以称作。两大目的
1.验证这个对象的某些方法的调用情况,调用了多少次,参数是什么等等
2.指定这个对象的某些方法的行为,返回特定的值,或者是执行特定的动作
1.1、测试替身分类
「测试替身」,它来源于电影中的特技替身的概念。Meszaros 在他的文中[2]定义了五类替身。
2、Mockito
方法 | 说明 |
---|---|
Mockito.mock(classToMock) |
模拟对象 |
Mockito.verify(mock) |
验证行为是否发生 |
Mockito.when(methodCall).thenReturn(value1).thenReturn(value2) |
触发时第一次返回value1,第n次都返回value2 |
Mockito.doThrow(toBeThrown).when(mock).[method] |
模拟抛出异常。 |
Mockito.mock(classToMock,defaultAnswer) |
使用默认Answer模拟对象 |
Mockito.when(methodCall).thenReturn(value) |
参数匹配 |
Mockito.doReturn(toBeReturned).when(mock).[method] |
参数匹配(直接执行不判断) |
Mockito.when(methodCall).thenAnswer(answer)) |
预期回调接口生成期望值 |
Mockito.doAnswer(answer).when(methodCall).[method] |
预期回调接口生成期望值(直接执行不判断) |
Mockito.spy(Object) |
用spy监控真实对象,设置真实对象行为 |
Mockito.doNothing().when(mock).[method] |
不做任何返回 |
Mockito.doCallRealMethod().when(mock).[method] |
调用真实的方法 |
Mockito.when(mock.[method]).thenCallRealMethod(); |
调用真实的方法,同上 |
reset(mock) |
重置mock |
2.1、Mockito.mock
:mock
出一个虚假的对象
@Test
public void test_1() {
PersonDTO person = Mockito.mock(PersonDTO.class);
}
2.2、Mockito.verify
:
2.2.1、验证方法调用没(关心参数)&次数
@Test
public void test2_1() {
PersonDTO person = Mockito.mock(PersonDTO.class);
// 1、验证person的getSex得到了调用
person.getSex(1);
Mockito.verify(person).getSex(1);
Mockito.verify(person, Mockito.times(1)).getSex(1);
}
方法 | 说明 |
---|---|
times(n) |
方法被调用n次 |
never() |
没有被调用 |
atLeast(n) |
至少被调用n次 |
atLeastOnce() |
至少被调用1次,相当于atLeast(1) |
atMost() |
最多被调用n次 |
2.2.2、验证方法调用没(不关系参数)
@Test
public void test_3() {
PersonDTO person = Mockito.mock(PersonDTO.class);
person.printing("healerjean");
// 1、只关心打印方法走没走,而不关心他的参数是什么的时候,我们就要用到Mock的any方法
Mockito.verify(person).printing(Mockito.anyString());
}
方法 | 说明 |
---|---|
anyString() |
表示任何一个字符串都可以 |
anyInt |
|
anyLong |
|
anyDouble |
|
anyObject |
表示任何对象 |
any(clazz) |
表示任何属于clazz的对象 |
anyCollection |
|
anyCollectionOf(clazz) |
|
anyList(Map, set) |
|
anyListOf(clazz) |
2.2.3、Mockito.inOrder
:验证调用顺序
@Test
public void test2_2(){
PersonDTO person = Mockito.mock(PersonDTO.class);
person.getSex(1);
person.isMan(1);
InOrder inOrder = Mockito.inOrder(person);
inOrder.verify(person).getSex(1);
inOrder.verify(person).isMan(1);
}
2.3、Mockito.when(xx).thenReturn(xx)
指定某个方法的返回值,或者是执行特定的动作
@Test
public void test_4_1() {
PersonDTO person = Mockito.mock(PersonDTO.class);
// 4.1、当调用person的isMan方法,同时传入"0"时,返回true
// (注意这个时候我们调用person.isMan(0);的时候值为true而调用其他数字则为false,
// 如果我们忽略数字,传任何值都返回true时,就可以用到我们上面讲的any()参数适配方法)
Mockito.when(person.isMan(0)).thenReturn(true);
// true
System.out.println(person.isMan(0));
// false
System.out.println(person.isMan(1));
//当调用person的isMan方法,同时传入"0"时,返回false,其他默认也都是 false
Mockito.when(person.isMan(0)).thenReturn(false);
// false
System.out.println(person.isMan(0));
// false
System.out.println(person.isMan(1));
Mockito.when(person.isMan(Mockito.anyInt())).thenReturn(true);
// true
System.out.println(person.isMan(0));
// true
System.out.println(person.isMan(1));
}
2.4、Mockito.doThrow
:
指定某法方法抛出异常
@Test
public void test_4_2() {
List list = Mockito.mock(List.class);
list.add("123");
//1、当list调用clear()方法时会抛出异常
Mockito.doThrow(new RuntimeException()).when(list).clear();
list.clear();
}
2.5、Mockito.doReturn
指定返回特定值
public void test_4_3() {
List list = Mockito.mock(List.class);
Mockito.doReturn("123").when(list).get(Mockito.anyInt());
System.out.println(list.get(0));
}
2.6、mock
对象默认不调用&真实调用
2.6.1、Mockito.doNothing()
:默认不调用
@Test
public void test_4_4(){
Foo foo = Mockito.mock(Foo.class);
//1、什么信息也不会打印, mock对象并不会调用真实逻辑
foo.doFoo();
//2、啥也不会打印出来
Mockito.doNothing().when(foo).doFoo();
foo.doFoo();
//不会调用真实逻辑,但是int默认值就是0,所以打印0
// 打印0
System.out.println(foo.getCount());
}
class Foo {
public void doFoo() {
System.out.println("method doFoo called.");
}
public int getCount() {
return 1;
}
}
2.6.2、Mockito.doCallRealMethod
:真实调用
@Test
public void test_4_4(){
Foo foo = Mockito.mock(Foo.class);
//3、这里会调用真实逻辑, 打印出信息
Mockito.doCallRealMethod().when(foo).doFoo();
// 打印:"method doFoo called."
foo.doFoo();
Mockito.doCallRealMethod().when(foo).getCount();
// 打印 0
System.out.println(foo.getCount());
}
class Foo {
public void doFoo() {
System.out.println("method doFoo called.");
}
public int getCount() {
return 1;
}
}
3、注解
⬤ 使用
@Mock
和@InjectMocks
在没有spring上下文的情况下运行测试,这是首选,因为它要快得多⬤ 使用
@SpringBootTest
或@SpringMvcTest
与@MockBean
一起启动spring
上下文以创建模拟对象,@Autowired
获取要测试的类的实例,模拟工具将用于其自动连接的依赖项。在为与数据库交互的代码编写集成测试或希望测试REST
API
时,可以使用此选项。
注解 | 说哦名 |
---|---|
@Mock |
创建一个Moc k,用于替换被测试类中的引用的 bean 或第三方类。 |
@InjectMocks |
用于创建一个被测试类的实例,当您希望 Mockito 创建一个对象的实例,并使用带有@Mock 注释的 mock 作为其依赖项时。 |
@Mockbean |
可用于将模拟对象添加到 Spring 应用程序上下文。mock 将替换应用程序上下文中相同类型的任何现有 bean 。如果没有定义相同类型的 bean ,将添加一个新的bean 。通常与@SpringBootTest 一起使用 |
3.1、非 Spring
Spring
:A
->B
->C
,这是一个Spring
链路,这里只单侧1层 A -> B 可以用
3.1.1、BaseJunit5MockitoTest
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
/**
* BaseJunit5MockitoTest
* @author zhangyujin
* @date 2023/3/23 17:34.
*/
@Slf4j
@ExtendWith(MockitoExtension.class)
public class BaseJunit5MockitoTest {
/**
* 所有测试方法运行前运行
*/
@BeforeAll
public static void beforeAll() {
log.info("[Junit5MockitoBaseTest#beforeAll] Run before all test methods run");
}
/**
* 每个测试方法运行前运行
*/
@BeforeEach
public void beforeEach() {
//增加改注解
log.info("[Junit5MockitoBaseTest#beforeEach] Run before each test method runs");
}
/**
* 每个测试方法运行完毕后运行
*/
@AfterEach
public void afterEach() {
log.info("[Junit5MockitoBaseTest#afterEach] Run after each test method finishes running");
}
/**
* 在所有测试方法运行完毕后运行
*/
@AfterAll
public static void afterAll() {
log.info("[Junit5MockitoBaseTest#afterAll] Run after all test methods have finished running");
}
}
3.1.2、Junit5MockitoTest
import com.healerjean.proj.BaseJunit5MockitoTest;
import com.healerjean.proj.service.service.CenterService;
import com.healerjean.proj.service.service.impl.TopServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
/**
* Junit5MockitoTest
* @author zhangyujin
* @date 2023/3/23 17:36.
*/
@Slf4j
public class Junit5MockitoTest extends BaseJunit5MockitoTest {
/**
* topService
*/
@InjectMocks
private TopServiceImpl topService;
@Mock
private CenterService centerService;
@DisplayName("Junit5MockitoTest.test")
@Test
public void test(){
when(centerService.centerMethod(anyString())).thenReturn("mockCenterMethod");
String result = topService.topMethod("HealerJean");
log.info("result:{}", result);
}
}
3.2、Spring
Spring
:A
->B
->C
,这是一个Spring
链路
3.2.1、BaseJunit5SpringTest
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
/**
* Junit5SpringBaseTest
* @author zhangyujin
* @date 2023/3/23 17:12.
*/
@Slf4j
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = HljJunitApplication.class)
@DisplayName("Junit5-SpringBootTest 基础类")
public class BaseJunit5SpringTest {
/**
* 非静态方法必须指定 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
*/
@BeforeAll
public void beforeAll() {
//when(demoPrcResource.rpcInvoke(any())).thenReturn(DemoPrcResourceMock.rpcInvokeReturn());
}
/**
* 所有测试方法运行前运行
*/
// @BeforeAll
// public static void beforeAll() {
// log.info("[Junit5BaseTest#beforeAll] Run before all test methods run");
// }
/**
* 每个测试方法运行前运行
*/
@BeforeEach
public void beforeEach() {
log.info("[Junit5BaseTest#beforeEach] Run before each test method runs");
}
/**
* 每个测试方法运行完毕后运行
*/
@AfterEach
public void afterEach() {
log.info("[Junit5BaseTest#afterEach] Run after each test method finishes running");
}
/**
* 在所有测试方法运行完毕后运行
*/
@AfterAll
public static void afterAll() {
log.info("[Junit5BaseTest#afterAll] Run after all test methods have finished running");
}
}
3.2.2、Junit5SpringTest
import com.healerjean.proj.BaseJunit5SpringTest;
import com.healerjean.proj.service.service.BottomService;
import com.healerjean.proj.service.service.TopService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import javax.annotation.Resource;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
/**
* Junit5SpringTest
* @author zhangyujin
* @date 2023/3/23 17:23.
*/
@Slf4j
public class Junit5SpringTest extends BaseJunit5SpringTest {
/**
* topService
*/
@Resource
private TopService topService;
@MockBean
private BottomService bottomService;
@DisplayName("topService.topMethod")
@Test
public void test(){
when(bottomService.bottomMethod(anyString())).thenReturn("mockBottomMethod");
String result = topService.topMethod("HealerJean");
log.info("result:{}", result);
}
}
3.3、Spring
项目内存初始化时候 mock
3.1.1、测试类
3.1.1.1、DemoRpcProxy
package com.healerjean.proj.service.rpc.proxy;
/**
* DemoConsumer
*
* @author zhangyujin
* @date 2023/6/15 21:22.
*/
public interface DemoRpcProxy {
/**
* Rpc调用
*
* @param msg
* @return String
*/
String rpcInvoke(String msg);java
}
3.1.1.2、DemoRpcProxyImpl
package com.healerjean.proj.service.rpc.proxy.impl;
import com.healerjean.proj.service.rpc.DemoPrcResource;
import com.healerjean.proj.service.rpc.proxy.DemoRpcProxy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* DemoRpcProxy
*
* @author zhangyujin
* @date 2023/6/15 21:23.
*/
@Slf4j
@Service("demoRpcProxy")
public class DemoRpcProxyImpl implements DemoRpcProxy {
@Resource
private DemoPrcResource demoPrcResource;
/**
* Rpc调用
*
* @param reqString reqString
* @return String
*/
@Override
public String rpcInvoke(String reqString) {
return demoPrcResource.rpcInvoke(reqString);
}
}
3.1.1.3、DemoPrcResource
package com.healerjean.proj.service.rpc;
import org.springframework.stereotype.Service;
/**
* DemoPrcResource
*
* @author zhangyujin
* @date 2023/6/15 21:29.
*/
@Service("demoPrcResource")
public class DemoPrcResource {
/**
* rpcInvoke
*
* @return String
*/
public String rpcInvoke(String reqStr) {
return reqStr + "远程接口";
}
}
3.1.2、BaseJunit5SpringTest
package com.healerjean.proj.base;
import com.healerjean.proj.TomcatLauncher;
import com.healerjean.proj.mock.DemoPrcResourceMock;
import com.healerjean.proj.rpc.provider.DemoPrcResource;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
/**
* Junit5SpringBaseTest
*
* @author zhangyujin
* @date 2023/3/23 17:12.
*/
@Slf4j
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ExtendWith(SpringExtension.class)
@DisplayName("Junit5-SpringBootTest 基础类")
@SpringBootTest(classes = TomcatLauncher.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class BaseJunit5SpringTest {
/**
* 1、使用@Resource 会有问题,不让 when
* 2、其他地方在使用的时候,用@Resource,不可以使用@MockBean了,因为会导致重复
*/
@MockBean
private DemoPrcResource demoPrcResource;
/**
* 非静态方法必须指定 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
*/
@BeforeAll
public void beforeAll() {
when(demoPrcResource.rpcInvoke(any())).thenReturn(DemoPrcResourceMock.rpcInvokeReturn());
}
/**
* 每个测试方法运行前运行
*/
@BeforeEach
public void beforeEach() {
}
/**
* 每个测试方法运行完毕后运行
*/
@AfterEach
public void afterEach() {
}
/**
* 在所有测试方法运行完毕后运行
*/
@AfterAll
public static void afterAll() {
}
}
3.1.3、BaseJunit5SpringTestImpl
package com.healerjean.proj.base.impl;
import com.healerjean.proj.base.BaseJunit5SpringTest;
import com.healerjean.proj.rpc.consumer.proxy.impl.DemoRpcProxyImpl;
import com.healerjean.proj.rpc.provider.DemoPrcResource;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import javax.annotation.Resource;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
/**
* BaseJunit5SpringTest
*
* @author zhangyujin
* @date 2023/6/15 21:53.
*/
@Slf4j
public class BaseJunit5SpringTestImpl extends BaseJunit5SpringTest {
/**
* demoRpcProxy
*/
@Resource
private DemoRpcProxyImpl demoRpcProxy;
@Resource
private DemoPrcResource demoPrcResource;
@DisplayName("BaseJunit5SpringTestImpl.test")
@Test
public void test1() {
String result = demoRpcProxy.rpcInvoke("success");
log.info("result:{}", result);
String result2 = demoRpcProxy.rpcInvoke("success");
log.info("result2:{}", result2);
}
@DisplayName("BaseJunit5MockitoTestImpl.test2")
@Test
public void test2() {
when(demoPrcResource.rpcInvoke(anyString())).thenReturn("test2MockMethod");
String result = demoRpcProxy.rpcInvoke("success");
log.info("result:{}", result);
String result2 = demoRpcProxy.rpcInvoke("success");
log.info("result2:{}", result2);
}
}
4、解决方案
4.1、配置文件读取不到
mock
不启动spring
,当需要使用配置文件中的值可通过反射工具类进行配置
//参数说明:1.bean名称 2.需要set的属性名 3.需要set的值
ReflectionTestUtils.setField(businessTradeOrderServiceImplUnderTest, "popOrderType", 109);
//依赖pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
4.2、mock
静态方法
一定要释放,否则会造成全局
mock
try (MockedStatic<SpringUtils> springUtilsMockedStatic = mockStatic(SpringUtils.class)) {
when(dictionaryService.judgeDictDataExist(any(), any())).thenReturn(true);
springUtilsMockedStatic.when(() -> SpringUtils.getBean(service.class)).thenReturn(dictionaryService);
boolean res = dictionaryIncludedValidator.isValid("ProductComprehend", null);
Assertions.assertTrue(res);
}
4.3、按照参数返回 mock
对象