测试框架01-MockMvc、Mock和Spy
测试框架01-MockMvc、Mock和Spy
1.MockMvc
@SpringBootTest
@AutoConfigureMockMvc // 将MockMvc注入到容器中
class UserControllerTest {
@Resource
private MockMvc mockMvc;
@Test
@DisplayName("通过id获取用户") // 为测试用例添加名称
void getUserByIdTest() throws Exception {
// 构建GET请求
RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/get/1");
mockMvc.perform(requestBuilder)
// 响应状态为200。
.andExpect(MockMvcResultMatchers.status().isOk())
// 响应的数据。
.andExpect(MockMvcResultMatchers.content().string(Matchers.equalTo("{\"id\":1,\"age\":null,\"name\":\"A01\",\"phone\":null,\"address\":null,\"info\":null}")))
// 输出详细的请求和响应的内容。
.andDo(MockMvcResultHandlers.print());
}
@Test
@DisplayName("通过用户名模糊搜索")
void getUserByNameSearch() throws Exception {
RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/getUserByNameSearch?name=1");
MvcResult mvcResult = mockMvc.perform(requestBuilder).andReturn();
// 获取响应
MockHttpServletResponse mockHttpServletResponse = mvcResult.getResponse();
System.out.println(mockHttpServletResponse.getStatus()); // 200
// {"id":1,"age":null,"name":"A01","phone":null,"address":null,"info":null}
System.out.println(mockHttpServletResponse.getContentAsString());
}
@Test
@DisplayName("保存用户")
void save() throws Exception {
// 构建POST请求。
RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/save")
// 请求数据。
.content("{\"id\":4,\"age\":null,\"name\":\"A04\",\"phone\":null,\"address\":null,\"info\":null}")
// 设置请求格式。
.contentType(MediaType.APPLICATION_JSON_VALUE);
mockMvc.perform(requestBuilder)
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
2.Mock
- Mock的简单使用。
class UserMapperTest01 {
@Test
@DisplayName("Mock的简单使用")
void getUserByIdTest() {
UserEntity userEntity = new UserEntity();
userEntity.setId(1);
// Mock出UserMapper对象
UserMapper userMapper = Mockito.mock(UserMapper.class);
// Mock出对象的类型,UserMapper$MockitoMock$aAjtLRsk
System.out.println(userMapper.getClass().getName());
// 打桩,调用userMapper.getUserById(1);时,永远返回userEntity。
Mockito.when(userMapper.getUserById(1)).thenReturn(userEntity);
Assertions.assertEquals(1, userMapper.getUserById(1).getId());
// 对于没有打桩的情况,返回默认值null,int类型的数据返回0。
Assertions.assertNull(userMapper.getUserById(0));
}
}
- Mock对象的创建。
class UserMapperTest02 {
/**
* 使用Mock注解创建对象,需要初始化Mock环境,即让Mock的对象UserMapper有值。
* 初始化Mock环境的两种方式:
* 1) 在测试前,执行MockitoAnnotations.openMocks(this);
* 2) 测试类上加,@RunWith(MockitoJUnitRunner.class)。
*/
@Mock
private UserMapper userMapper;
@BeforeEach
void beforeEach() {
MockitoAnnotations.openMocks(this);
}
@Test
@DisplayName("通过Mock注解创建对象,Mock对象方法参数匹配。")
void getUserByIdTest() {
// Mockito.anyInt(),任何参数都返回null。
Mockito.when(userMapper.getUserById(Mockito.anyInt())).thenReturn(null);
Assertions.assertNull(userMapper.getUserById(0));
Assertions.assertNull(userMapper.getUserById(1));
}
@Test
@DisplayName("Mock异常")
void getUserByIdThrowsException() {
Mockito.when(userMapper.getUserById(Mockito.anyInt())).thenThrow(new RuntimeException("运行时错误"));
Assertions.assertThrowsExactly(RuntimeException.class, () -> userMapper.getUserById(1));
// 对于没有返回值的方法,不能使用thenThrow()来抛出异常,可以使用doThrow()来抛出异常。
Mockito.doThrow(new RuntimeException("运行时错误")).when(userMapper).getUserById(1);
Assertions.assertThrowsExactly(RuntimeException.class, () -> userMapper.getUserById(1));
}
@Test
@DisplayName("Mock出多个返回值")
void getUserByIdMoreReturnValue() {
UserEntity userEntity01 = new UserEntity();
userEntity01.setId(1);
UserEntity userEntity02 = new UserEntity();
userEntity02.setId(2);
UserEntity userEntity03 = new UserEntity();
userEntity03.setId(3);
Mockito.when(userMapper.getUserById(Mockito.anyInt())).thenReturn(userEntity01, userEntity02, userEntity03);
// thenReturn()有多个参数,调用时会依次返回每个参数,超过参数数量的调用会返回最后一个参数。
Assertions.assertEquals(userEntity01, userMapper.getUserById(1));
Assertions.assertEquals(userEntity02, userMapper.getUserById(2));
Assertions.assertEquals(userEntity03, userMapper.getUserById(3));
Assertions.assertEquals(userEntity03, userMapper.getUserById(3));
}
}
3.Spy
/**
* Spy和Mock的区别,Spy会走真实的方法。
*/
class UserServiceTest01 {
@Test
@DisplayName("Spy的简单使用")
void getUserById() {
UserServiceImpl userService = Mockito.spy(UserServiceImpl.class);
// Spy会走真实的方法,userService.getUserById(1)中依赖UserMapper,所有抛出空指针异常。
Assertions.assertThrowsExactly(NullPointerException.class, () -> userService.getUserById(1));
}
@Test
@DisplayName("Spy打桩")
void getUserByIdBySpyMock() {
UserEntity userEntity01 = new UserEntity();
userEntity01.setId(1);
UserServiceImpl userService = Mockito.spy(UserServiceImpl.class);
// Spy出的对象打桩之后,就不会走真实的方法了,而是会返回打桩的默认值。
Mockito.doReturn(userEntity01).when(userService).getUserById(Mockito.anyInt());
Assertions.assertEquals(userEntity01, userService.getUserById(1));
}
}
4.InjectMocks
class UserServiceTest02 {
@Mock
private UserMapper userMapper;
@InjectMocks // @InjectMocks会将@Mock和@Spy出的对象注入到当前对象中。
private UserServiceImpl userService;
@BeforeEach
void beforeEach() {
MockitoAnnotations.openMocks(this);
}
@Test
@DisplayName("测试获取用户id")
void getUserById() {
// 通过@InjectMocks注解将userService内部依赖的userMapper注入,
// 所以userService.getUserById(1);在调用时没有抛出空指针异常。
UserEntity userEntity = userService.getUserById(1);
// 由于userMapper是Mock出来的,所以方法的返回值为默认值null。
Assertions.assertNull(userEntity);
UserEntity userEntity01 = new UserEntity();
userEntity01.setId(1);
// 给userMapper.getUserById()方法打桩,所以相当于改变了userService.getUserById(1);的返回值。
Mockito.doReturn(userEntity01).when(userMapper).getUserById(Mockito.anyInt());
Assertions.assertEquals(userEntity01, userService.getUserById(1));
}
}
5.Mockito的其他方法
class UserServiceTest02 {
@Mock
private UserMapper userMapper;
@InjectMocks // @InjectMocks会将@Mock和@Spy出的对象注入到当前对象中。
private UserServiceImpl userService;
@BeforeEach
void beforeEach() {
MockitoAnnotations.openMocks(this);
}
/**
* thenAnswer()和then(),功能相同,都可以根据不同的参数返回不同的值。
*/
@Test
@DisplayName("thenAnswer()和then()")
void test01() {
UserEntity userEntity01 = new UserEntity();
userEntity01.setId(1);
Mockito.when(userMapper.getUserById(Mockito.anyInt())).thenAnswer(new Answer<UserEntity>() {
@Override
public UserEntity answer(InvocationOnMock invocation) throws Throwable {
// 获取方法执行的参数
Object[] arguments = invocation.getArguments();
int arg = (int) arguments[0];
if (arg == 1) {
return userEntity01;
}
return null;
}
});
Assertions.assertEquals(userEntity01, userMapper.getUserById(1));
Assertions.assertNull(userMapper.getUserById(10));
}
/**
* doNothing() 让void方法什么都不做。
* Mock出的对象,本来就说明都不做,所有更适合于Spy创建出的对象。
*/
@Test
@DisplayName("doNothing()")
void test02() {
UserServiceImpl userService = Mockito.spy(UserServiceImpl.class);
Mockito.doNothing().when(userService).update(null);
}
/**
* reset() 重置Mock对象,让其返回默认值。
*/
@Test
@DisplayName("reset()")
void test03() {
UserEntity userEntity01 = new UserEntity();
userEntity01.setId(1);
Mockito.doReturn(userEntity01).when(userMapper).getUserById(Mockito.anyInt());
Assertions.assertEquals(userEntity01, userMapper.getUserById(1));
// reset()后,Mock出的对象会返回默认值。
Mockito.reset(userMapper);
Assertions.assertNull(userMapper.getUserById(1));
}
/**
* thenCallRealMethod() 让Spy的某个方法调用真实的对象。
*/
@Test
@DisplayName("thenCallRealMethod()")
void test04() {
UserServiceImpl userService = Mockito.spy(UserServiceImpl.class);
// Spy创建UserServiceImpl在getUserById()是会抛出空指针异常,现在让其返回null。
Mockito.doReturn(null).when(userService).getUserById(Mockito.anyInt());
Assertions.assertNull(userService.getUserById(1));
// 使用doCallRealMethod()后调用真实的方法,所以会抛出空指针异常。
Mockito.doCallRealMethod().when(userService).getUserById(Mockito.anyInt());
Assertions.assertThrowsExactly(NullPointerException.class, () -> userService.getUserById(1));
}
/**
* verify() 验证是否调用某个方法。
*/
@Test
void test05() {
// 验证这个方法从没有被调用过
Mockito.verify(userMapper, Mockito.never()).getUserById(1);
userMapper.getUserById(1);
// 验证这个方法至少调用了1次
Mockito.verify(userMapper, Mockito.atLeastOnce()).getUserById(1);
userMapper.getUserById(1);
// 验证当前方法调用了2次
Mockito.verify(userMapper, Mockito.times(2)).getUserById(1);
}
/**
* 验证对象是否是Spy或者Mock对象。
*/
@Test
void test06() {
UserServiceImpl spy = Mockito.spy(UserServiceImpl.class);
UserServiceImpl mock = Mockito.mock(UserServiceImpl.class);
Assertions.assertTrue(Mockito.mockingDetails(spy).isSpy());
Assertions.assertTrue(Mockito.mockingDetails(mock).isMock());
}
}
6.MockBean和SpyBean
@SpringBootTest
class UserServiceTest03 {
// @MockBean,会替换Spring容器中出现的相同类型的类,并且调用方法会走真实的方法。
@SpyBean
private UserMapper userMapper;
// @MockBean,会替换Spring容器中出现的相同类型的类,并且调用方法不会走真实的方法。
@MockBean
private IUserService userService;
@Autowired
private ApplicationContext applicationContext;
@Test
void getUserByIdMockBean() {
// com.rainbow.test01.service.IUserService$MockitoMock$1x9g3fQ2
System.out.println(userService.getClass().getName());
IUserService bean = applicationContext.getBean(IUserService.class);
// com.rainbow.test01.service.IUserService$MockitoMock$6JqkL7SC
System.out.println(bean.getClass().getName());
// @MockBean创建的对象,和@Mock创建的对象相同,也不会调用真实的方法,每个方法都会返回默认值。
Assertions.assertNull(userService.getUserById(1));
UserEntity userEntity = new UserEntity();
// 打桩,@MockBean和@Mock的用法一样。
Mockito.doReturn(userEntity).when(userService).getUserById(1);
Assertions.assertEquals(userEntity, userService.getUserById(1));
}
@Test
void getUserByIdSpyBean() {
// com.rainbow.test01.mapper.UserMapper$MockitoMock$5OalTLnn$$EnhancerBySpringCGLIB$$4acf6361
System.out.println(userMapper.getClass().getName());
Assertions.assertEquals(1, userMapper.getUserById(1).getId());
}
}
7.自动化测试
- 测试可以简单的分为单元测试、集成测试、性能测试等。
- 单元测试顾名思义对一个小的单元进行测试,不需要考虑Spring环境、数据库等,常常可以使用Mock、Spy注解来完成。
- 集成测试。集成测试就需要依赖于Spring上下文、数据库等,常常可以使用MockBean、SpyBean来完成。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现