依赖
spring-boot-starter-test
依赖中包含junit5和mokito核心包。springboot的2.6.15版本的mokito-core的版本是4.0.0.
mockito-inline
用于静态成员的mock,从mokito的3.4.0版本开始支持。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.6.15</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>4.0.0</version>
<scope>test</scope>
</dependency>
@SpringBootTest
@SpringBootTest
引入spring的运行时环境时使用,一般的单元测试使用mock的数据即可,不需要连接数据库、redis等。
@Test
@Test
标识方法为一个单元测试
@Mock
全模拟一个虚拟数据,类似数据库连接等不好构造的数据,然后配合Mokito的API来打桩,当什么方法调用时返回什么值。
@Mock
模拟的出来的对象在打桩后的动作只会在当前test方法中生效。
部分模拟一个虚拟数据,也可以配合Mokito的API来打桩,当什么方法调用时返回什么值。
@InjectMocks
@InjectMocks模拟对象并将@Mock、@Spy模拟的对象自动注入到自己的成员变量中。
无Spring
在单独测试一些需要Mock数据但不需要使用Spring提供的上下文相关的功能时,只需要初始化mock数据即可。单测类继承这个BaseTest
基类,实现Mock对象的初始化,避免重复代码。
jupiter包下
public abstract class BaseTest {
private AutoCloseable closeable;
@BeforeEach
void beforeEach() {
closeable = openMocks(this);
@AfterEach
void releaseMocks() throws Exception {
closeable.close();
有Spring
在单独测试一些需要Mock数据且需要使用Spring提供的上下文相关的功能时,比如测API接口。
import com.xrj.security.UserUtils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.mockito.MockedStatic;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
@SpringBootTest
public abstract class BaseMvcTest {
protected MockMvc mockMvc;
@Autowired
private WebApplicationContext webApplicationContext;
@BeforeEach
void before() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
Mokito打桩
实例方法打桩
打桩:模拟对象的行为返回预先设定好的值或做出预设的行为。常用的打桩逻辑有when和doNothing两种:
when(xxx.doSome()).thenReturn(yyy)
当xxx
对象调用doSome
方法时返回yyy
,只有有返回值的方法可以使用这个。
doNothing().when(xxx).doSome()
当xxx
对象调用doSome
方法时什么也不做,只有没有返回值的方法可以使用这个。
比如现在有个SomeService的Bean,有PlanDao和UserService两个成员变量,现在是测试SomeService.doSome()
方法,其内依次调用了planDao.findDetail
和userService.saveAction
,测试时只想测doSome
内的逻辑而不想findDetail
和saveAction
走真实的逻辑,那么此时用下面打桩的方法就可以在someService.doSome()
调用时,让对应调用findDetail
和saveAction
的地方进行按照我们预设的逻辑返回,这样就达到目的了。
class SomeTest extends BaseTest{
@Mock
private PlanDao planDao;
@Mock
private UserService userService;
@InjectMocks
private SomeService someService;
@test
void testDoSome(){
List<Plan> list = Lists.newArrayList();
//当planDao调用findDetail方法参数为1时返回list列表,list是自行初始化的
when(planDao.findDetail(1)).thenReturn(list);
//当userService调用saveAction方法时什么也不做
doNothing().when(userService).saveAction(action);
//调用被测方法
Some actual = someService.doSome();
静态方法打桩
在早期的mokito中不支持静态打桩的,需要配合PowerMock依赖。
从mokito的3.4.0版本后,新增了mockito-inline
扩展包用于支持静态方法的打桩,方便对静态方法进行打桩。
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>4.0.0</version>
<scope>test</scope>
</dependency>
UserUtils类下有一个getCurrentUserId的静态方法,此时就给这个方法进行打桩,让其返回结果为1
class SomeTest extends BaseTest{
@test
void testDoSome(){
//mock类
MockedStatic<UserUtils> mockedUserUtils = mockStatic(UserUtils.class);
//打桩,getCurrentUserId方法是一个静态方法
mockedUserUtils.when(UserUtils::getCurrentUserId).thenReturn(1);
//调用被测方法
Some actual = someService.doSome();
MockedStatic底层继承了AutoCloseable,使用完毕需要close。所以可以将静态工具类的初始化和清理放在单测基础类中。
public abstract class BaseTest {
private AutoCloseable closeable;
protected static MockedStatic<UserUtils> mockedUserUtils;
@BeforeAll
static void setUp() {
mockedUserUtils = mockStatic(UserUtils.class);
@AfterAll
static void clear(){
mockedUserUtils.close();
@BeforeEach
void beforeEach() {
closeable = openMocks(this);
@AfterEach
void releaseMocks() throws Exception {
closeable.close();
Junit5中的断言由Assertions
类提供,类似Junit4中的Assert
。一般是用于判断被测试方法的返回值是否符合预期。
//断言预期值和实际值结果相等
Assertions.assertEquals(expected, actual);
//断言方法调用后抛出指定异常
Assertions.assertThrows(MyExecption.class, () -> someService.doSome());
Junit5中的行为验证功能由verify
API提供,验证被测试方法中的细节行为是否符合预期。
//验证在被测试的方法中planDao.findDetail(1)调用过一次。
verify(planDao,times(1)).findDetail(1);
class SomeTest extends BaseTest{
@Mock
private PlanDao planDao;
@Mock
private UserService userService;
@InjectMocks
private SomeService someService;
@test
void testDoSome(){
Some exepected = new Some();
List<Plan> list = Lists.newArrayList();
//当planDao调用findDetail方法参数为1时返回list列表,list是自行初始化的
when(planDao.findDetail(1)).thenReturn(list);
//当userService调用saveAction方法时什么也不做
doNothing().when(userService).saveAction(action);
//调用被测方法
Some actual = someService.doSome();
//断言实际返回值和预期值一致
Assertions.assertEquals(expected, actual);
//验证在被测试的方法中planDao.findDetail(1)调用过一次。
verify(planDao,times(1)).findDetail(1);
MockMvc
MockMvc就是单测基类需要使用Spring上下文的场景,使用前需要初始化Spring上下文。
@MockBean
在MockMvc测试时是模拟http调用,此时测试controller接口时可以使用@MockBean
注解注入service实例,将Service实例方法进行打桩测试,避免单元测试影响到数据库。
测试API
SomeController
中有接口/doSome
可以接收参数name
,/doSome
接口中调用someService.findSome()
并将返回值作为接口响应值。
class SomeControllerTest extends BaseMvcTest {
@MockBean
private SomeService someService;
@Test
void testDoSome() throws Exception {
Some expected = new Some();
Mockito.when(someService.findSome()).thenReturn(expected));
//发起mvc调用
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders
.get("/doSome")
.accept(MediaType.APPLICATION_JSON)
.characterEncoding(StandardCharsets.UTF_8)
.param("name", "test")
).andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
//验证返回值
String resStr = mvcResult.getResponse().getContentAsString();
Some actual = JSON.parseObject(resStr, Some.class);
Assertions.assertEquals(expected, actual);