目录
- 一、单元测试用途
- 二、Mock测试介绍
- 三、Mock测试所需依赖
- 四、Mock的核心功能
- 五、单元测试生成插件
- 1、IDEA中安装Squaretest插件使用
- 2、破解插件过程
一、单元测试用途
1、日常开发团队要求规范,需要对开发需求代码进行单元测试并要求行覆盖率达到要求,DevOps流水线也会开设相关门禁阀值阻断代码提交,一般新增代码行覆盖率80%左右。
二、Mock测试介绍
1、Mock是为了解决不同的单元之间由于耦合而难于开发、测试的问题。所以,Mock既能出现在单元测试中,也会出现在集成测试、系统测试过程中。Mock 最大的功能是帮你把单元测试的耦合分解开,如果你的代码对另一个类或者接口有依赖,它能够帮你模拟这些依赖,并帮你验证所调用的依赖的行为。
2、Mock 测试就是在测试活动中,对于某些不容易构造或者不容易获取的比较复杂的数据/场景,用一个虚拟的对象(Mock对象)来创建用于测试的测试方法。
3、Mock重要作用
Mock是为了解决不同的单元之间由于耦合而难于开发、测试的问题。所以,Mock既能出现在单元测试中,也会出现在集成测试、系统测试过程中。
Mock 最大的功能是帮你把单元测试的耦合分解开,如果你的代码对另一个类或者接口有依赖,它能够帮你模拟这些依赖,并帮你验证所调用的依赖的行为。
三、Mock测试所需依赖
1、主要引入mockito-core/powermock-core
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>4.5.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-core</artifactId> <version>2.0.9</version> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>2.0.9</version> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito2</artifactId> <version>2.0.9</version> <scope>test</scope> </dependency>
Mockito和PowerMock都是单元测试模拟框架,用于模拟被测试类的依赖项。Mockito基于动态代理的方式实现,而PowerMock在Mockito基础上增加了类加载器以及字节码篡改技术,使其可以实现对private/static/final方法的Mock
四、Mock的核心功能
1、Mock对象创建
Mockito.mock(List.class); // Mock对象创建
public class VerifyMockExample { @Test public void testVerifyMock() { javascript List<String> mockList = Mockito.mock(List.class); // 调用Mock对象的方法 mockList.add("testCode"); mockList.size(); // 验证mockList.add("test")是否被调用过一次 Mockito.verify(mockList,Mockito.times(1)).add("testCode"); // 验证size()方法是否被调用过 Mockito.verify(mockList,Mockito.times(1)).size(); Assert.assertFalse(mockList.size()==1); } }
verify系列方法
Mockito.verify/Mockito.times()验证调用次数
·verify(mphpock).methodCall():验证方法被调用
· verify(mock, times(n)).methodCall():验证方法被调用n次
· verify(mock, never()).methodCall():验证方法从未被调用
或者是通过注解来实现创建
@Mock private UserInfoMapper mockUserInfoMapper; @InjectMocks private UserInfoServiceImpl userInfoServiceImplUnderTest;
@Test public void testVerifyMock2(){ List<String> mockList = Mockito.mock(List.class); mockList.add("Code1"); mockList.add("Code2"); // 验证是否调用两次 Mockito.verify(mockList,Mockito.times(2)); }
// 验证这个方法从没有被调用过 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);
Mockito.when()使用when和thenReturn方法配置Mock对象的行为
Mockito.when( 对象.方法名 ).thenReturn( 自定义结果) //当调用了某个 Mock 对象的方法时,就回传我们想要的自定义结果。
thenReturn系列方法
//当使用任何整数值调用 userService 的 getUsepythonrById 方法时,就回传一个名字为 I'm mock 3 的 User 对象。
Mockito.when(userService.getUserById(Mockito.anyInt)).thenReturn( newUser( 3, "I'm mock"));//限制只有当参数的数字是 3 时,才会回传名字为 I'm mock 3 的 user 对象。Mockito.when(userService.getUserById( 3)).thenReturn( newUser( 3, "I'm mock"));//当调用 userService 的 insertUser 方法时,不管传进来的 user 是什么,都回传 100。Mockito.when(userService.insertUser(Mockito.any(User.class))).thenReturn( 100);
thenThrow系列方法
//当调用 userService 的 getUserById 时的参数是 8 时,抛出一个 RuntimeException。
Mockito.when(userService.getUserById( 8)).thenThrow( new RuntimeException( "mock throw exception"));//如果方法没有返回值的话(即方法定义为 public void myMethod {...}),要改用 doThrow 抛出 Exception。Mockito.doThrow( new RuntimeException( "mock throw exception")).when(userService.print);
@Test public void testVerifyMock3(){ List<String> mockList = Mockito.mock(List.class); // 设置Mock对象的预期行为 Mockito.when(mockList.get(0)).thenReturn("mockedValue"); // 断言验证返回值 Assert.assertEquals("mockedValue",mockList.get(0)); }
实战案例:测试一个UserInfoServiceImpl层saveUser()方法.
原始方法:
@Transactional @Override public void saveUser(UserInfo userInfo){ userInfoMapper.saveUser(userInfo); }
单元测试:
@Test public void testSaveUser() { // Setup final UserInfo userInfo = new UserInfo(); userInfo.setId(0); userInfo.setUserName("userName"); userInfo.setCreateTime(new GregorianCalendar(2020, Calendar.JANUARY, 1).getTime()); userInfo.setUpdateTime(new GregorianCalendar(2020, Calendar.JANUARY, 1).getTime()); // Run the test userInfoServiceImplUnderTest.saveUser(userInfo); // Verify the results Mockito.verify(mockUserInfoMapper).saveUser(new UserInfo()); }
原始方法:
@Override public List<UserInfo> queryListByUserName(String userName) { return userInfoMapper.queryListByUserName(userName); }
单元测试方法:
@Test public void testQueryListByUserName() { // Setup final UserInfo userInfo = new UserInfo(); userInfo.setId(0); userInfo.setUserName("userName"); userInfo.setCreateTime(new GregorianCalendar(2020, Calendar.JANUARY, 1).getTime()); userInfo.setUpdateTime(new GregorianCalendar(2020, Calendar.JANUARY, 1).getTime()); final List<UserInfo> expectedResult = Arrays.asList(userInfo); // Configure UserInfoMapper.queryListByUserName(...). final UserInfo userInfo1 = new UserInfo(); userInfo1.setId(0); userInfo1.setUserName("userName"); userInfo1.setCreateTime(new GregorianCalendar(2020, Calendar.JANUARY, 1).getTime()); userInfo1.setUpdateTime(new GregorianCalendar(2020, Calendar.JANUARY, 1).getTime()); final List<UserInfo> userInfos = Arrays.asList(userInfo1); when(mockUserInfoMapper.queryListByUserName("userName")).thenReturn(userInfos); // Run the test final List<UserInfo> result = userInfoServiceImplUnderTest.queryListByUserName("userName"); // Verify the results assertThat(result).isEqualTo(expectedResult); } @Test public void testQueryListByUserName_UserInfoMapperReturnsNoItems() { // Setup when(mockUserInfoMapper.queryListByUserName("userName")).thenReturn(Collections.emptyList()); // Run the test final List<UserInfo> result = userInfoServiceImplUnderTest.queryListByUserName("userName"); // Verify the results assertThat(result).isEqualTo(Collections.emptyList()); }
原始方法:
@Override public List<UserInfo> queryUserInfoList(String createTime, List<Integer> idList) { return userInfoMapper.queryListByIds(createTime,idList); }
单元测试方法:
@Test public void testQueryUserInfoList() { // Setup final UserInfo userInfo = new UserInfo(); userInfo.setId(0); userInfo.setUserName("userName"); userInfo.setCreateTime(new GregorianCalendar(2020, Calendar.JANUARY, 1).getTime()); userInfo.setUpdateTime(new GregorianCalendar(2020, Calendar.JANUARY, 1).getTime()); final List<UserInfo> expectedResult = Arrays.asList(userInfo); // Configure UserInfoMapper.queryListByIds(...). final UserInfo userInfo1 = new UserInfo(); userInfo1.setId(0); userInfo1.setUserName("userName"); userInfo1.setCreateTime(new GregorianCalendar(2020, Calendar.JANUARY, 1).getTime()); userInfo1.setUpdateTime(new GregorianCalendar(2020, Calendar.JANUARY, 1).getTime()); final List<UserInfo> userInfos = Arrays.asList(userInfo1); when(mockUserInfoMapper.queryListByIds("createTime", Arrays.asList(0))).thenReturn(userInfos); // Run the test final List<UserInfo> result = userInfoServiceImplUnderTest.queryUserInfoList("createTime", Arrays.asList(0)); // Verify the results assertThat(result).isEqualTo(expectedResult); } @Test public void testQueryUserInfoList_UserInfoMapperReturnsNoItems() { // Setup when(mockUserInfoMapper.queryListByIds("createTime", Arrays.asList(0))).thenReturn(Collections.emptyList()); // Run the test final List<UserInfo> result = userInfoServiceImplUnderTest.queryUserInfoList("createTime", Arrays.asList(0)); // Verify the results assertThat(result).isEqualTo(Collections.emptyList()); }
原始方法:Controller层,MockMvc
mockMvc.perform(request):执行一个HTTP请求,并返回ResultActions对象。
ResultActions.andExpect(expected):验证请求的处理结果,如状态码、响应体等。ResultActions.andDo(handler):处理请求的响应,如将响应体写入文件等。ResultActions.andReturn():返回已执行请求的结果,以便直接访问结果。MockMvc.perform(request).andExpect(expected).andDo(handler).andReturn():链式调用,执行请求、验证结果并处理响应,返回结果。
@Slf4j @RestController public class UserInfoController { @Autowired private UserInfoService userInfoService; /** * 查询全部列表 * @return */ @RequestMapping("/boot/query/users") public List<UserInfo> getUserInfoList(){ List<UserInfo> list = userInfoService.list(); return list; } /** * 保存用户信息 * @return */ @RequestMapping("/boot/save/user") public String saveUser(){ try { UserInfo user=new UserInfo(); user.setUserName("MyBATis Log Free"); userInfoService.save(user); } catch (Exception e) { log.error("save user error", e); throw new GlobalException("save user error"); } return "success"; } @RequestMapping("/boot/query/users/ids") public List<UserInfo> getUserInfoListIds(){ List<Integer> list = Arrays.asList(1, 3, 5); String createTime="2022-09-05 15:11:21"; List<UserInfo> userInfos = userInfoService.queryUserInfoList(createTime,list); return userInfos; } @RequestMapping("/boot/query/users/like") public List<UserInfo> getUserInfoListLike(){ List<UserInfo> infoList = userInfoService.queryListByUserName("A"); return infoList; } @RequestMapping("/boot/save/userinfo") public void saveUserInfo(){ UserInfo userInfo = new UserInfo(); userInfo.setId(5); userInfo.setUserName("Puck"); Date startTime = new Date(); Date endTime = new Date(); userInfo.setCreateTime(startTime); userInfo.setUpdateTime(endTime); userInfoService.saveUser(userInfo); } }
单元测试方法如下:
@RunWith(SpringRunner.class) @WebMvcTest(UserInfoController.class) public class UserInfoControllerTest { @Autowired private MockMvc mockMvc; @MockBean private UserInfoService mockUserInfoService; @Test public void testGetUserInfoList() throws Exception { // Setup // Configure UserInfoService.list(...). final UserjsInfo userInfo = new UserInfo(); userInfo.setId(0); userInfo.setUserName("MyBatis Log Free"); userInfo.setCreateTime(new GregorianCalendar(2020, Calendar.JANUARY, 1).getTime()); userInfo.setUpdateTime(new GregorianCalendar(2020, Calendar.JANUARY, 1).getTime()); final List<UserInfo> userInfos = Arrays.asList(userInfo); when(mockUserInfoService.list()).thenReturn(userInfos); // Run the test final MockHttpServletResponse response = mockMvc.perform(get("/boot/query/users") .accept(MediaType.APPLICATION_jsON)) .andReturn().getResponse(); // Verify the results assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); // assertThat(response.getContentAsString()).isEqualTo("expectedResponse"); } @Test public void testGetUserInfoList_UserInfoServiceReturnsNoItems() throws Exception { // Setup when(mockUserInfoService.list()).thenReturn(Collections.emptyList()); // Run the test final MockHttpServletResponse response = mockMvc.perform(get("/boot/query/users") .accept(MediaType.APPLICATION_JSON)) .andReturn().getResponse(); // Verify the results assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); assertThat(response.getContentAsString()).isEqualTo("[]"); } @Test public void testSaveUser() throws Exception { // Setup when(mockUserInfoService.save(new UserInfo())).thenReturn(false); // Run the test final MockHttpServletResponse response = mockMvc.perform(get("/boot/save/user") .accept(MediaType.APPLICATION_JSON)) .andReturn().getResponse(); // Verify the results assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); assertThat(response.getContentAsString()).isEqualTo("expectedResponse"); verify(mockUserInfoService).save(new UserInfo()); } @Test public void testGetUserInfoListIds() throws Exception { // Setup // Configure UserInfoService.queryUserInfoList(...). final UserInfo userInfo = new UserInfo(); userInfo.setId(0); userInfo.setUserName("MyBatis Log Free"); http://www.devze.com userInfo.setCreateTime(new GregorianCalendar(2020, Calendar.JANUARY, 1).getTime()); userInfo.setUpdateTime(new GregorianCalendar(2020, Calendar.JANUARY, 1).getTime()); final List<UserInfo> userInfos = Arrays.asList(userInfo); when(mockUserInfoService.queryUserInfoList("2022-09-05 15:11:21", Arrays.asList(0))).thenReturn(userInfos); // Run the test final MockHttpServletResponse response = mockMvc.perform(get("/boot/query/users/ids") .accept(MediaType.APPLICATION_JSON)) .andReturn().getResponse(); // Verify the results assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); assertThat(response.getContentAsString()).isEqualTo("expectedResponse"); } @Test public void testGetUserInfoListIds_UserInfoServiceReturnsNoItems() throws Exception { // Setup when(mockUserInfoService.queryUserInfoList("2022-09-05 15:11:21", Arrays.asList(0))) .thenReturn(Collections.emptyList()); // Run the test final MockHttpServletResponse response = mockMvc.perform(get("/boot/query/users/ids") .accept(MediaType.APPLICATION_JSON)) .andReturn().getResponse(); // Verify the results assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); assertThat(response.getContentAsString()).isEqualTo("[]"); } @Test public void testGetUserInfoListLike() throws Exception { // Setup // Configure UserInfoService.queryListByUserName(...). final UserInfo userInfo = new UserInfo(); userInfo.setId(0); userInfo.setUserName("MyBatis Log Free"); userInfo.setCreateTime(new GregorianCalendar(2020, Calendar.JANUARY, 1).getTime()); userInfo.setUpdateTime(new GregorianCalendar(2020, Calendar.JANUARY, 1).getTime()); final List<UserInfo> userInfos = Arrays.asList(userInfo); when(mockUserInfoService.queryListByUserName("A")).thenReturn(userInfos); // Run the test final MockHttpServletResponse response = mockMvc.perform(get("/boot/query/users/like") .accept(MediaType.APPLICATION_JSON)) .andReturn().getResponse(); // Verify the results assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); assertThat(response.getContentAsString()).isEqualTo("expectedResponse"); } @Test public void testGetUserInfoListLike_UserInfoServiceReturnsNoItems() throws Exception { // Setup when(mockUserInfoService.queryListByUserName("A")).thenReturn(Collections.emptyList()); // Run the test final MockHttpServletResponse response = mockMvc.perform(get("/boot/query/users/like") .accept(MediaType.APPLICATION_JSON)) .andReturn().getResponse(); // Verify the results assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); assertThat(response.getContentAsString()).isEqualTo("[]"); } @Test public void testSaveUserInfo() throws Exception { // Setup // Run the test final MockHttpServletResponse response = mockMvc.perform(get("/boot/save/userinfo") .accept(MediaType.APPLICATION_JSON)) .andReturn().getResponse(); // Verify the results assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); assertThat(response.getContentAsString()).isEqualTo("expectedResponse"); verify(mockUserInfoService).saveUser(new UserInfo()); } }
@Mock用于模拟不属于 Spring 上下文的对象,而@MockBean用于模拟属于 Spring Boot 应用程序中的 Spring 上下文的对象。@MockBean提供与 Spring Boot 测试框架的无缝集成,并允许在测试期间用模拟对象轻松替换实际 bean
Mockito提供了多种参数匹配器(Matchers)用于更灵活的验证和配置行为:
import static org.mockito.ArgumentMatchers.*; when(mockRepository.findById(anyInt())).thenReturn(Optional.of(user)); verify(mockRepository).findById(eq(1));
常见的匹配器包括:
·any():匹配任何参数
· anyInt():匹配任何整数参数
· eq(value):匹配特定值
· isNull():匹配null值
· notNull():匹配非null值
Mock异常
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));
@MockBean 是 Spring Boot 提供的注解,它用于在测试中创建一个 mock 对象,并将其注入到 Spring 上下文中,替换掉原来的真实 Bean。 需要使用SpringJUnit4ClassRunner.class之类的注解
@Mock: 用于代替Mockito.mock创建mock对象,创建一个Mock实例,需要基于JUnit5环境。@InjectMocks: 创建一个实例,其余用@Mock(或@Spy)注解创建的mock将被注入到用该实例中。
你要测试哪个类(如TemplateUserServiceImpl ),那么就用 @InjectMocks注解;被测试的类中通过 @Autowired注解注入了几个,那么测试类里面就用@Mock注解创建几个实例!
原始代码:
@Service public class TemplateUserServiceImpl implements TemplateUserService { @Autowired private NamedParameterJdbcTemplate jdbcTemplate; @Transactional @Override public void saveUser() { UserInfo userInfo = new UserInfo(); userInfo.setId(7); userInfo.setUserName("Boot"); Date startTime = new Date(); Date endTime = new Date(); userInfo.setCreateTime(startTime); userInfo.setUpdateTime(endTime); // JdbcTemplate的写入datetime,使用in方式 // String sql="insert into user_info(user_name,create_time,update_time) values(:user_name,:create_time,:update_time)"; String sql="insert into user_info(user_name,create_time,update_time) values(:userName,:createTime,:updateTime)"; SqlParameterSource sqlParameterSource=new BeanPropertySqlParameterSource(userInfo); // HashMap paramMap = new HashMap<>(); // paramMap.put("user_name",userInfo.getUserName()); // paramMap.put("create_time",userInfo.getCreateTime()); // paramMap.put("update_time",userInfo.getUpdateTime()); // jdbcTemplate.update(sql,paramMap); jdbcTemplate.update(sql,sqlParameterSource); } }
对应单元测试代码:
import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.SqlParameterSource; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class TemplateUserServiceImplTest { @Mock private NamedParameterJdbcTemplate mockJdbcTemplate; @InjectMocks private TemplateUserServiceImpl templateUserServiceImplUnderTest; @Test public void testSaveUser() { // Setup // Run the test templateUserServiceImplUnderTest.saveUser(); // Verify the results verify(mockJdbcTemplate).update( eq("insert into user_info(user_name,create_time,update_time) values(:userName,:createTime,:updateTime)"), any(SqlParameterSource.class)); } @Test public void testSaveUser_NamedParameterJdbcTemplateThrowsDataAccessException() { // Setup when(mockJdbcTemplate.update( eq("insert into user_info(user_name,create_time,update_time) values(:userName,:createTime,:updateTime)"), any(SqlParameterSource.class))).thenThrow(DataAccessException.class); // Run the test assertThatThrownBy(() -> templateUserServiceImplUnderTest.saveUser()).isInstanceOf(DataAccessException.class); } }
@MockBean 和 @SpyBean和@Spy以及@Mock的使用场景和区别
五、单元测试生成插件
1、IDEA中安装Squaretest插件使用
文件右键即可生成对应单元测试,需要修改测试用例,满足业务诉求。
2、破解插件过程
idea版本:
下载字节码编译工具:jclasslib。
jclasslib下载地址
JAR包路径:C:\Users\Administrator\AppData\Roaming\JetBrains\IntelliJIdea2023.3\plugins\Squaretest
说明:不用版本的Squaretest插件的jar包名称或许不一样,找空间最大的那个,约15M左右的那个。
最后一步点击保存按钮,选择“overwrite”时,此时一定要将idea关闭,否则会保存失败的.
最后破解成功,可以正常使用。
到此这篇关于Mockito+PowerMock+Junit单元测试的文章就介绍到这了,更多相关Mockito+PowerMock+Junit单元测试内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论