赞
踩
此文根据视频《mockito加junit搞定单元测试》进行整理,如有侵权,联系删除。
要进行测试的方法存在外部依赖(如db,redis,第三方接口调用等),为了能够专注于对该方法(单元)的逻辑进行测试,就希望能虚拟出外部依赖,避免外部依赖成为测试的阻塞项。
用到mock类框架进行虚拟这些外部依赖。
一般单元测试都是针对service层。
mock类框架:用于mock外部依赖
名称:ito:input to output
官网:https://site.mockito.org
官网文档:https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html
限制:老版本对于final class、final method、static method、private method 均不能被 mockito mock。目前已支持final class、final method、static method 的 mock,具体可以参考官网
(39. Mocking final types, enums and final methods (Since 2.1.0))
(48. New API for mocking static methods (Since 3.4.0))
原理:bytebuddy 教程:https://www.bilibili.com/video/BV1G24y1a7bd
(通过修改字节码来实现代理的)
官网:https://github.com/powermock/powermock
与 mockito 的版本支持关系:https://gitee.com/mirrors/powermock/wikis/Mockito#supported-versions
对 mockito 或 easymock 的增强
方法插桩 | 方法不插桩 | 作用对象 | 最佳实践 | |
---|---|---|---|---|
mock对象 | 执行插桩逻辑 | 返回mock对象的默认值(0 / null / 空集合) | 类、接口 | 被测试类或其依赖 |
spy对象 | 执行插桩逻辑 | 调用真实方法 | 类、接口 | 被测试类 |
使用 mockingDetails 判断对象是否为 mock对象、spy 对象
方法一 | 方法二 | 方法三 | |
---|---|---|---|
junit4 | @RunWith(MockitoJUnitRunner.class) +@Mock等注解 | Mockito.mock(X.class)等静态方法 | MockitoAnnotations.openMocks(this)+@Mock等注解 |
junit5 | @ExtendWith(MockitoExtension.class) + @Mock等注解 | Mockito.mock(X.class)等静态方法 | MockitoAnnotations.openMocks(this)+@Mock等注解 |
/** * 初始化mock/spy对象有3种方式,第1种方式 */ @RunWith(MockitoJUnitRunner.class) public class InitMockOrSpyMethod1Test { @Mock private UserService mockUserService; @Spy private UserService spyUserService; @Test public void test1() { // true System.out.println("Mockito.mockingDetails(mockUserService).isMock() = " + Mockito.mockingDetails(mockUserService).isMock()); // false System.out.println("Mockito.mockingDetails(mockUserService).isSpy() = " + Mockito.mockingDetails(mockUserService).isSpy()); // true System.out.println("Mockito.mockingDetails(spyUserService).isMock() = " + Mockito.mockingDetails(spyUserService).isMock()); // true System.out.println("Mockito.mockingDetails(spyUserService).isSpy() = " + Mockito.mockingDetails(spyUserService).isSpy()); } }
/** * 初始化mock/spy对象有3种方式,第2种方式 */ public class InitMockOrSpyMethod2Test { private UserService mockUserService; private UserService spyUserService; @Before public void init() { mockUserService = Mockito.mock(UserService.class); spyUserService = Mockito.spy(UserService.class); } @Test public void test1() { // true System.out.println("Mockito.mockingDetails(mockUserService).isMock() = " + Mockito.mockingDetails(mockUserService).isMock()); // false System.out.println("Mockito.mockingDetails(mockUserService).isSpy() = " + Mockito.mockingDetails(mockUserService).isSpy()); // true System.out.println("Mockito.mockingDetails(spyUserService).isMock() = " + Mockito.mockingDetails(spyUserService).isMock()); // true System.out.println("Mockito.mockingDetails(spyUserService).isSpy() = " + Mockito.mockingDetails(spyUserService).isSpy()); } }
/** * 初始化mock/spy对象有3种方式,第3种方式 */ public class InitMockOrSpyMethod3Test { @Mock private UserService mockUserService; @Spy private UserService spyUserService; @Before public void init() { // MockitoAnnotations.initMocks(this); MockitoAnnotations.openMocks(this); } @Test public void test1() { // true System.out.println("Mockito.mockingDetails(mockUserService).isMock() = " + Mockito.mockingDetails(mockUserService).isMock()); // false System.out.println("Mockito.mockingDetails(mockUserService).isSpy() = " + Mockito.mockingDetails(mockUserService).isSpy()); // true System.out.println("Mockito.mockingDetails(spyUserService).isMock() = " + Mockito.mockingDetails(spyUserService).isMock()); // true System.out.println("Mockito.mockingDetails(spyUserService).isSpy() = " + Mockito.mockingDetails(spyUserService).isSpy()); } }
/** * 参数匹配:通过方法签名(参数)来指定哪些方法调用需要被处理(插桩、verify验证) */ @RunWith(MockitoJUnitRunner.class) public class ParamMatcherTest { @Mock private UserService mockUserService; /** * 对于mock对象,不会调用真实方法,直接返回mock对象的默认值 * 默认值:0(int)、null、空集合 */ @Test public void test1() { UserVO userVO = mockUserService.selectById(1L); // userVO = null System.out.println("userVO = " + userVO); UserUpdateReq userUpdateReq = new UserUpdateReq(); int i = mockUserService.modifyById(userUpdateReq); // i = 0 System.out.println("i = " + i); } /** * 测试插桩时的参数匹配,只拦截userUpdateReq1 */ @Test public void test2() { UserUpdateReq userUpdateReq1 = new UserUpdateReq(); userUpdateReq1.setId(1L); userUpdateReq1.setPhone("1"); Mockito.doReturn(99).when(mockUserService).modifyById(userUpdateReq1); int result1 = mockUserService.modifyById(userUpdateReq1); // result1 = 99 System.out.println("result1 = " + result1); UserUpdateReq userUpdateReq2 = new UserUpdateReq(); userUpdateReq2.setId(2L); userUpdateReq2.setPhone("2"); int result2 = mockUserService.modifyById(userUpdateReq2); // result2 = 0 System.out.println("result2 = " + result2); int result3 = mockUserService.modifyById(userUpdateReq1); // result3 = 99 System.out.println("result3 = " + result3); } /** * 测试插桩时的参数匹配,ArgumentMatchers.any(UserUpdateReq.class)拦截UserUpdateReq类型的任意对象 * 除了ArgumentMatchers.any(XXX.class),还有anyXXX(),例如anyString(),anyLong()... */ @Test public void test3() { Mockito.doReturn(99).when(mockUserService).modifyById(ArgumentMatchers.any(UserUpdateReq.class)); UserUpdateReq userUpdateReq1 = new UserUpdateReq(); userUpdateReq1.setId(1L); userUpdateReq1.setPhone("1"); Mockito.doReturn(99).when(mockUserService).modifyById(userUpdateReq1); int result1 = mockUserService.modifyById(userUpdateReq1); // result1 = 99 System.out.println("result1 = " + result1); UserUpdateReq userUpdateReq2 = new UserUpdateReq(); userUpdateReq2.setId(2L); userUpdateReq2.setPhone("2"); int result2 = mockUserService.modifyById(userUpdateReq2); // result2 = 99 System.out.println("result2 = " + result2); int result3 = mockUserService.modifyById(userUpdateReq1); // result3 = 99 System.out.println("result3 = " + result3); } /** * 测试插桩时的参数匹配,除了ArgumentMatchers.any(XXX.class),还有anyXXX(),例如anyString(),anyLong()... * 注意:anyXXX()不包括null */ @Test public void test4() { List<String> featureList = new ArrayList<>(); featureList.add("高"); featureList.add("富"); mockUserService.add("zhangsan", "123", featureList); mockUserService.add("wangwu", "789", featureList); // mockUserService.add("zhangsan", "456", null); Mockito.verify(mockUserService, Mockito.times(1)).add("zhangsan", "123", featureList); Mockito.verify(mockUserService, Mockito.never()).add("lisi", "123", featureList); Mockito.verify(mockUserService, Mockito.times(2)).add(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyList()); // 不允许部分使用anyXXX表达式 // Mockito.verify(mockUserService, Mockito.times(2)).add(ArgumentMatchers.anyString(), "123", featureList); } }
指定调用某个方法时的行为(stubbing),达到相互隔离的目的
@RunWith(MockitoJUnitRunner.class) public class StubTest { @Mock private List<String> mockList; @Mock private UserServiceImpl mockUserServiceImpl; @Spy private UserServiceImpl spyUserServiceImpl; /** * 指定返回值 */ @Test public void testReturn() { // 方法1 Mockito.doReturn("zero").when(mockList).get(0); Assert.assertEquals("zero", mockList.get(0)); // 方法2 Mockito.when(mockList.get(1)).thenReturn("one"); Assert.assertEquals("one", mockList.get(1)); } /** * void返回值方法插桩 */ @Test public void testVoid() { // 调用mockList.clear时候什么都不做 Mockito.doNothing().when(mockList).clear(); mockList.clear(); // 验证调用了一次clear Mockito.verify(mockList, Mockito.times(1)).clear(); } /** * 插桩的两种方式 */ @Test public void testReturnMethod() { Mockito.when(mockUserServiceImpl.getNumber()).thenReturn(99); // 99 System.out.println("mockUserServiceImpl.getNumber() = " + mockUserServiceImpl.getNumber()); Mockito.when(spyUserServiceImpl.getNumber()).thenReturn(99); // 会打印getNumber // 再打印99 // spy对象在没有插桩时候是调用真实方法的,写在when中会导致先执行一次原方法,达不到mock的目的 System.out.println("spyUserServiceImpl.getNumber() = " + spyUserServiceImpl.getNumber()); Mockito.doReturn(999).when(spyUserServiceImpl).getNumber(); // 99 System.out.println("spyUserServiceImpl.getNumber() = " + spyUserServiceImpl.getNumber()); } /** * 抛出异常 */ @Test public void testThrowException() { // 方法有返回值 Mockito.when(mockList.get(ArgumentMatchers.anyInt())).thenThrow(RuntimeException.class); // Mockito.doThrow(RuntimeException.class).when(mockList).get(ArgumentMatchers.anyInt()); try { mockList.get(4); Assert.fail(); } catch (Exception e) { Assert.assertTrue(e instanceof RuntimeException); } // 方法没返回值 Mockito.doThrow(RuntimeException.class).when(mockList).clear(); try { mockList.clear(); Assert.fail(); } catch (Exception e) { Assert.assertTrue(e instanceof RuntimeException); } } /** * 多次插桩 */ @Test public void testMultipleStub() { Mockito.when(mockList.size()).thenReturn(1).thenReturn(2).thenReturn(3); // Mockito.when(mockList.size()).thenReturn(1, 2, 3); Assert.assertEquals(1, mockList.size()); Assert.assertEquals(2, mockList.size()); Assert.assertEquals(3, mockList.size()); Assert.assertEquals(3, mockList.size()); } /** * 指定实现逻辑的插桩 * thenAnswer可以实现对方法进行插桩,以实现自定义返回值逻辑。我们只需实现函数式接口Answer,并实现自定义返回值逻辑即可。 * 泛型表示要插桩方法的返回值类型,此处我们使用String */ @Test public void testThenAnswer() { Mockito.when(mockList.get(ArgumentMatchers.anyInt())).thenAnswer(new Answer<String>() { @Override public String answer(InvocationOnMock invocationOnMock) throws Throwable { Integer argument = invocationOnMock.getArgument(0, Integer.class); return String.valueOf(argument * 100); } }); String result = mockList.get(3); Assert.assertEquals("300", result); } /** * 执行真正的原始方法 */ @Test public void testCallRealMethod() { // 对mock对象插桩让它执行原始方法 Mockito.when(mockUserServiceImpl.getNumber()).thenCallRealMethod(); int mockResult = mockUserServiceImpl.getNumber(); Assert.assertEquals(100, mockResult); // 不对spy对象插桩,spy对象默认就会调用真实方法 int spyResult = spyUserServiceImpl.getNumber(); Assert.assertEquals(100, spyResult); // 如果不想spy对象调用真实方法,则需要对它进行插桩 Mockito.doReturn(999).when(spyUserServiceImpl).getNumber(); spyResult = spyUserServiceImpl.getNumber(); Assert.assertEquals(999, spyResult); } /** * 测试verify */ @Test public void testVerify() { mockList.add("one"); mockList.add("two"); mockList.clear(); Mockito.verify(mockList).add("one"); Mockito.verify(mockList, Mockito.times(1)).add("one"); Mockito.verify(mockList, Mockito.times(2)).add(ArgumentMatchers.anyString()); Mockito.verify(mockList, Mockito.never()).size(); Mockito.verify(mockList, Mockito.times(0)).size(); } }
@RunWith(MockitoJUnitRunner.class) public class InjectMocksTest { // 执行test1方法报错,因为被@InjectMocks注解标注的类必须是实现类,mockito会创建对应的实例对象 // @InjectMocks // private UserService userService; // 默认创建的对象就是未经过mockito处理的普通对象 // @InjectMocks // private UserServiceImpl userService; // 配合@Spy注解使其变成默认调用真实方法的对象 @InjectMocks @Spy private UserServiceImpl userService; // 使用@Mock注解注入到@InjectMocks注解对应的实例对象中 // 如果不使用@Mock注解注入userFeatureService到userService中,执行test1方法过程中,userFeatureService是null @Mock private UserFeatureService userFeatureService; @Test public void test1() { int number = userService.getNumber(); Assert.assertEquals(100, number); } }
hamcrest:junit4中引入的第三方断言库,junit5中被移出,从1.3版本后,坐标由org.hamcrest:hamcrest-core变为org.hamcrest:hamcrest,用的少
assertj:常用的断言库
junit4原生断言
junit5原生断言
生成的对象受spring管理
@MockBean
@SpyBean
create database mockito_demo CHARACTER SET utf8mb4; use mockito_demo; drop table if exists user; CREATE TABLE `user` ( `id` bigint NOT NULL AUTO_INCREMENT comment '主键', `username` varchar(100) NOT NULL comment '用户名称', `phone` varchar(50) NOT NULL comment '电话', PRIMARY KEY (`id`) ) ENGINE=InnoDB comment '用户表'; drop table if exists user_feature; CREATE TABLE `user_feature` ( `id` bigint NOT NULL AUTO_INCREMENT comment '主键', `user_id` bigint NOT NULL comment '用户id:用户表的主键', `feature_value` varchar(150) NOT NULL comment '用户的特征值', PRIMARY KEY (`id`) ) ENGINE=InnoDB comment '用户特征表'; insert into user(username, phone) values('xiaoming','12345678912'); insert into user_feature(user_id,feature_value) values(1,'abc'); insert into user_feature(user_id,feature_value) values(1,'def'); insert into user_feature(user_id,feature_value) values(1,'ghi');
@SpringBootApplication
@EnableTransactionManagement
@MapperScan("com.lm.mockito.mapper")
public class MockitoApp {
public static void main(String[] args) {
SpringApplication.run(MockitoApp.class);
}
}
@RestController @Validated public class UserController { @Resource private UserService userService; @GetMapping("/selectById") public UserVO selectById(@NotNull Long userId) { return userService.selectById(userId); } @PostMapping("/add") public String add(@RequestBody @Validated UserAddReq addReq) { userService.add(addReq.getUserName(), addReq.getPhone(), addReq.getFeatureValueList()); return "OK"; } }
public interface UserService extends IService<UserDO> {
UserVO selectById(Long userId);
void add(String userName, String phone, List<String> featureValueList);
int modifyById(UserUpdateReq userUpdateReq);
int getNumber();
}
@Service public class UserServiceImpl extends ServiceImpl<UserMapper, UserDO> implements UserService { @Resource private UserFeatureService userFeatureService; @Override public UserVO selectById(Long userId) { UserDO existedEntity = getById(userId); if (existedEntity == null) { return null; } UserVO userVO = new UserVO(); BeanUtil.copyProperties(existedEntity, userVO); List<UserFeatureDO> featureList = userFeatureService.selectByUserId(userId); if (CollectionUtils.isEmpty(featureList)) { return userVO; } userVO.setFeatureValueList(featureList.stream().map(UserFeatureDO::getFeatureValue).collect(Collectors.toList())); return userVO; } @Override public void add(String userName, String phone, List<String> featureValueList) { UserDO userDO = new UserDO(); userDO.setUserName(userName); userDO.setPhone(phone); save(userDO); List<UserFeatureDO> userFeatureDOList = featureValueList.stream().map(featureValue -> { UserFeatureDO userFeatureDO = new UserFeatureDO(); userFeatureDO.setUserId(userDO.getId()); userFeatureDO.setFeatureValue(featureValue); return userFeatureDO; }).collect(Collectors.toList()); userFeatureService.saveBatch(userFeatureDOList); } @Override public int modifyById(UserUpdateReq userUpdateReq) { UserDO userDO = new UserDO(); userDO.setId(userUpdateReq.getId()); userDO.setUserName(userUpdateReq.getUserName()); userDO.setPhone(userUpdateReq.getPhone()); boolean successFlag = updateById(userDO); return successFlag ? 1 : -1; } @Override public int getNumber() { System.out.println("getNumber"); return 100; } }
public interface UserFeatureService extends IService<UserFeatureDO> {
List<UserFeatureDO> selectByUserId(Long userId);
}
@Service
public class UserFeatureServiceImpl extends ServiceImpl<UserFeatureMapper, UserFeatureDO> implements UserFeatureService {
@Override
public List<UserFeatureDO> selectByUserId(Long userId) {
if (Objects.isNull(userId)) {
return null;
}
LambdaQueryWrapper<UserFeatureDO> lqw = Wrappers.<UserFeatureDO>lambdaQuery().eq(UserFeatureDO::getUserId,
userId);
return list(lqw);
}
}
public interface UserMapper extends BaseMapper<UserDO> {
}
public interface UserFeatureMapper extends BaseMapper<UserFeatureDO> {
}
@Data
public class UserAddReq {
@NotBlank
private String userName;
@NotBlank
private String phone;
@NotEmpty
private List<String> featureValueList;
}
@Data
public class UserUpdateReq {
@NotNull
private Long id;
@NotBlank
private String userName;
@NotBlank
private String phone;
}
@Data
public class UserVO {
private Long id;
private String userName;
private String phone;
private List<String> featureValueList;
}
@Data
@TableName("user")
public class UserDO {
@TableId(type = IdType.AUTO)
private Long id;
private String userName;
private String phone;
}
@Data
@TableName("user_feature")
public class UserFeatureDO {
@TableId(type = IdType.AUTO)
private Long id;
private Long userId;
private String featureValue;
}
真要mock私有方法,使用powermock
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。