🎄Mockito概述
Mock 测试就是在测试过程中,对于某些不容易构造(如 HttpServletRequest 必须在Servlet 容器中才能构造出来)或者不容易获取比较复杂的对象(如 JDBC 中的ResultSet 对象),用一个虚拟的对象(Mock 对象)来创建以便测试的测试方法。 Mock 最大的功能是帮你把单元测试的耦合分解开,如果你的代码对另一个类或者接口有依赖,它能够帮你模拟这些依赖,并帮你验证所调用的依赖的行为。
🎄学习使用Mockito
🍭导入Mockito模块
<!-- 20231104 mockito-inline 支持测试静态方法 -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>4.3.1</version>
</dependency>
<!-- 20231104 导入JUnit依赖包 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
</dependency>
🍭使用Mockito的准备
🌴创建被测试类
创建一个被测试类 包含非静态方法和静态方法
package bbm.com.mockito;
/**
@author Liu Xianmeng
@createTime 2023/11/4 15:09
@instruction 学习Mockito的使用
*/
@SuppressWarnings({"all"})
public class Calculations {
public int add(int num1, int num2) {
System.out.println("C Calculations M add()方法被执行..");
return num1 + num2;
}
/**
* 测试静态方法 getANumberRangeList
*/
public static List<Integer> getANumberRangeList(int start, int end) {
// 返回一个数字区间
return IntStream.range(start, end).boxed().collect(Collectors.toList());
}
/**
* 测试静态方法 getAStr
*/
public static String getAStr() {
System.out.println("C Calculations M getAStr()方法被执行..");
return "bigibgmeng";
}
}
🌴创建测试类CalculationsTest
创建方法
创建结果(这个截图中 我已经把所有测试方法写完)
🍭Mockito的具体使用·测试普通方法
前置方法配置Mock对象
首先 声明一个需要使用的mock对象
import org.mockito.*;
...
@Mock
Calculations mockCalculations; // 声明一个Mock对象
如果要使用
@Mock
声明mock出来的对象 那么就需要对其进行初始化 需要添加一个setUp
方法 这个setUp方法的作用就是初始化@Mock
和@Spy(后面会讲到@Spy注解)修饰的属性,即虚拟的java对象,对应上文的mockCalculations
对象
// 测试方法执行前的动作
@BeforeEach
void setUp() {
// 初始化Mock的对象 注意使用方式
MockitoAnnotations.openMocks(this);
}
接下来就可以使用mockCalculations
对象了
使用**@Mock**不打桩测试 -> 不会执行测试对象的原方法
不会执行测试对象的原方法 是指被测试的类的方法 比方说被测试类的add方法中有一句🎯日志 这句日志在使用@Mock标注的对象进行测试的时候 并不会打印出来 也就是说 被测试类的add方法不会被执行 被执行的是模拟出来的方法
public int add(int num1, int num2) {
🎯System.out.println("C Calculations M add()方法被执行..");
return num1 + num2;
}
具体如何使用**@Mock**不打桩测试?看下面的代码 测试类CalculationsTest
中添加下面的方法
/****************** 测试非静态方法 ***************/
/**
* 使用@Mock不打桩测试 -> 不会执行测试对象的原方法
*/
@Test
void mockCalculationsWithoutStub() {
//Mockito.doReturn(100).when(mockCalculations).add(20, 80); // 不打桩 注释掉
int rst = mockCalculations.add(20, 80);
// 断言结果是否符合预期 -> 🎯不打桩则默认返回0
Assertions.assertEquals(0, rst);
// 断言add方法的执行次数是否为 ${wantedNumberOfInvocations}
Mockito.verify(mockCalculations, Mockito.times(1)).add(20, 80);
}
测试结果:
使用**@Mock**打桩测试 -> 不会执行测试对象的原方法
上面提到了打桩 那什么是打桩?那上面的
mockCalculationsWithoutStub
方法来说 就是下面这句代码做的事(上面被注释的那一行代码):Mockito.doReturn(100).when(mockCalculations).add(20, 80);
这句代码相当于预设了
mockCalculations
对象被调用后所做的回应 这句话的意思就是当mockCalculations
的add方法被调用且传入(20,80)参数的时候会返回100
具体的打桩测试如下(就是把被注释的打桩代码放开执行):
/**
* 使用@Mock打桩测试 -> 不会执行测试对象的原方法
*/
@Test
void mockCalculationsWithStub() {
Mockito.doReturn(100).when(mockCalculations).add(20, 80);
int rst = mockCalculations.add(20, 80);
// 断言结果是否符合预期
Assertions.assertEquals(100, rst);
// 断言add方法的执行次数是否为 ${wantedNumberOfInvocations}
Mockito.verify(mockCalculations, Mockito.times(1)).add(20, 80);
}
测试同样会通过
使用@Spy不打桩测试 -> 会执行测试对象的原方法
上面提到过
@Spy
注解 现在来说说它如何使用 简而言之 就是🎯用@Spy
标注的模拟对象 在不打桩的情况下❗❗❗会默认执行待测类的真实方法
首先 添加一个@Spy修饰的属性
@Spy
Calculations spyCalculations; // 声明一个Spy对象
然后写一个方法来使用这个模拟的spyCalculations
对象
/**
* 使用@Spy不打桩测试 -> ❗❗❗会执行测试对象的原方法
*/
@Test
void spyCalculationsWithoutStub() {
//Mockito.doReturn(100).when(spyCalculations).add(20, 80); // 不打桩 注释掉
int rst = spyCalculations.add(20, 80);
// 断言结果是否符合预期
Assertions.assertEquals(100, rst);
// 断言add方法的执行次数是否为 ${wantedNumberOfInvocations}
Mockito.verify(spyCalculations, Mockito.times(1)).add(20, 80);
}
执行结果:
使用@Spy打桩测试 -> 不会执行测试对象的原方法
/**
* 使用@Spy打桩测试 -> 不会执行测试对象的原方法
*/
@Test
void spyCalculationsWithStub() {
Mockito.doReturn(100).when(spyCalculations).add(20, 80);
int rst = spyCalculations.add(20, 80);
// 断言结果是否符合预期
Assertions.assertEquals(100, rst);
// 断言add方法的执行次数是否为 ${wantedNumberOfInvocations}
Mockito.verify(spyCalculations, Mockito.times(1)).add(20, 80);
}
🍭Mockito的使用·测试静态方法
/************* 测试静态方法 ***************/
/**
* 不会执行原方法
*/
@Test
void getANumberRangeList() {
// 🎯执行完自动释放资源
try(MockedStatic<Calculations> calculationsMockedStatic = Mockito.mockStatic(Calculations.class)) {
calculationsMockedStatic.when(() -> Calculations.getANumberRangeList(100, 102)).thenReturn(Arrays.asList(100, 101, 102));
/****** 这种方法不可用 ******/
//Mockito.doReturn(Arrays.asList(100, 101, 102)).when(Calculations.getANumberRangeList(100, 102));
// 执行静态方法
List<Integer> rst = Calculations.getANumberRangeList(100, 102);
System.out.println("C CalculationsTest M getANumberRangeList() -> rst = " + rst);
Assertions.assertTrue(rst.contains(101));
Assertions.assertTrue(rst.size() == 3);
}
}
/**
* 不会执行原方法
*/
@Test
void getAStr() {
// 🎯执行完自动释放calculationsMockedStatic对象资源
try(MockedStatic<Calculations> calculationsMockedStatic = Mockito.mockStatic(Calculations.class)) {
calculationsMockedStatic.when(Calculations::getAStr).thenReturn("bigbigmeng");
Assertions.assertEquals("bigbigmeng", Calculations.getAStr());
}
}
🍭初始化mock/spy对象的3种方式
1️⃣第一种
/**
* 第一种 使用 @ExtendWith(MockitoExtension.class) 注解标注测试类
*/
@ExtendWith(MockitoExtension.class)
public class InitMockOrSpyMethod1Test {
@Mock
private UserService mockUserService;
@Spy
private UserService spyUserService;
@Test
public void test1(){
...
}
}
2️⃣第二种
/**
* 第二种 使用@BeforeEach注解 + Mockito.mock(TargetClass.class)
*/
public class InitMockOrSpyMethod2Test {
private UserService mockUserService;
private UserService spyUserService;
@BeforeEach
public void init(){
mockUserService = Mockito.mock(UserService.class);
spyUserService = Mockito.spy(UserService.class);
}
@Test
public void test1(){
...
}
}
3️⃣第三种
/**
* 第3种 使用MockitoAnnotations.openMocks(this)
*/
public class InitMockOrSpyMethod3Test {
@Mock
private UserService mockUserService;
@Spy
private UserService spyUserService;
@BeforeEach
public void init(){
// 识别本类中的@Mock 或 @Spy 等mockito注解并创建对应的对象
MockitoAnnotations.openMocks(this);
}
@Test
public void test1(){
...
}
}
🍭断言的多种方式
when(mockList.size()).thenReturn(999);
// 测试hamcrest的断言
MatcherAssert.assertThat(mockList.size(), IsEqual.equalTo(999));
// 测试 assertJ assertThat:参数为实际的值
Assertions.assertThat(mockList.size()).isEqualTo(999);
// junit5原生断言
org.junit.jupiter.api.Assertions.assertEquals(999,mockList.size());
// junit4原生断言
org.junit.Assert.assertEquals(999,mockList.size());
🍭@InjectMocks的使用
- 被@InjectMocks标注的属性必须是实现类,因为mockito会创建对应的实例对象,默认创建的对象就是未经过mockito处理的普通对象,因此常配合@Spy注解使其变为默认调用真实方法的mock对象,被测试的类一般都需要标记这俩注解
- mockito会使用spy对象或mock对象注入到@InjectMocks对应的实例对象中
@ExtendWith(MockitoExtension.class)
public class InjectMocksTest {
@InjectMocks
@Spy
private UserServiceImpl userService;
@Mock
private UserFeatureService userFeatureService;
@Test
public void test1() {
int number = userService.getNumber();
Assertions.assertEquals(0,number);
}
}
🍭参数匹配
对于mock对象不会调用真实方法 直接返回mock对象的默认值 空
/**
* 对于mock对象不会调用真实方法,直接返回mock对象的默认值:
* 默认值(int)、null(UserVO)、空集合(List)
*/
@Test
public void test1(){
UserVO userVO = mockUserService.selectById(1L);
System.out.println("userVO = " + userVO); // null
UserUpdateReq userUpdateReq1 = new UserUpdateReq();
int i = mockUserService.modifyById(userUpdateReq1);
System.out.println("i = " + i); // 0
}
ArgumentMatchers
ArgumentMatchers.any拦截UserUpdateReq类型的任意对象 下面的例子演示不管传入的是userUpdateReq1还是userUpdateReq2返回的都是99
@Test
public void test3(){
// 拦截UserUpdateReq的任意对象 -> 不管参数传入什么 都会返回99
Mockito.doReturn(99).when(mockUserService).modifyById(ArgumentMatchers.any(UserUpdateReq.class));
UserUpdateReq userUpdateReq1 = new UserUpdateReq();
userUpdateReq1.setId(1L);
userUpdateReq1.setPhone("1L");
int result1 = mockUserService.modifyById(userUpdateReq1);
System.out.println("result1 = " + result1); // 99
UserUpdateReq userUpdateReq2 = new UserUpdateReq();
userUpdateReq2.setId(2L);
userUpdateReq2.setPhone("2L");
int result2 = mockUserService.modifyById(userUpdateReq2);
System.out.println("result2 = " + result2);// 99
}
除了any,还有anyXx(anyLong,anyString…) 注意它们都不包括null
🍭插桩总结
两种方法指定返回值
@Test
public void test1() {
// 方法一
doReturn("zero").when(mockList).get(0);
// 方法二
when(mockList.get(1)).thenReturn("one");
}
void返回值方法插桩
@Test
public void test2() {
// 调用mockList.method1的时候什么也不做
doNothing().when(mockList).method1();
mockList.method1();
// 验证调用了一次method1
verify(mockList, times(1)).method1();
}
插桩抛出异常
@Test
public void test4() {
// 方法一
doThrow(RuntimeException.class).when(mockList).clear();
try {
mockList.clear();
// 走到下面这行说明插桩失败了
Assertions.fail();
}catch (Exception e) {
// 断言表达式为真
Assertions.assertTrue(e instanceof RuntimeException);
}
// 方法二
when(mockList.get(anyInt())).thenThrow(RuntimeException.class);
try {
mockList.get(4);
Assertions.fail();
}catch (Exception e) {
Assertions.assertTrue(e instanceof RuntimeException);
}
}
模拟对象执行真正的原始方法
thenCallRealMethod()
@Test
public void test7(){
// 对mock对象插桩让他执行原始方法
when(mockUserServiceImpl.getNumber()).thenCallRealMethod();
int number = mockUserServiceImpl.getNumber();
Assertions.assertEquals(0,number);
// spy对象默认就会调用真实方法,如果不想让它调用,需要单独为它进行插桩
int spyResult = spyUserServiceImpl.getNumber();
Assertions.assertEquals(0,spyResult);
// 不让spy对象调用真实方法
doReturn(1000).when(spyUserServiceImpl).getNumber();
spyResult = spyUserServiceImpl.getNumber();
Assertions.assertEquals(1000,spyResult);
}
🎄Mockito实战
接下来使用Mockito对项目的后端接口进行实战测试
🍭要测试的接口如下
这是一个较为复杂的登录接口 流程如下
/**
* 用户登录 流程如下
*
* 1 判断Redis是否存在此用户的登录信息 如果存在则直接返回
* 2 检查登录数据是否存在空
* 3 检查密码长度是否过短
* 4 账户不能包含特殊字符
* 5 判断这个用户是否存在
* 6 核验密码是否正确
* 7 ticket == null 将用户的登录信息放入Redis并返回用户信息给前端
* 8 返回最终的成功登录结果 并返回用户信息给前端
*
* @param ur 必要的登录信息 账号和密码
* @param response response.addCookie(cookie) -> 将登录凭证ticket放到客户端cookie
* @param ticket 浏览器上的Cookie是针对具体的服务器而言的 当用户访问一个网站时
* 服务器会发送带有Cookie的响应头给浏览器存储 浏览器会将这些Cookie保存在用户的计算机上
* 以便在用户再次访问相同的网站时 可以将Cookie通过请求头发送给服务器进行身份验证或其他操作
* @return
*/
public ResponseEntity login(UserLogin ul, HttpServletResponse response, String ticket) {
logger.info("C UserServiceImpl M login().. ticket = " + ticket);
// 1 判断Redis是否存在此用户的登录信息 如果存在则直接返回
// 🎯这个例子中暂不测试redisTemplate部分的代码
//if(!StringUtils.isBlank(ticket) && !StringUtils.isBlank(ul.getUserAccount())) {
// Object o = redisTemplate.opsForValue().get(ticket);
// if(o != null) {
// HashMap<String, Object> map = new HashMap<>();
// map.put("userInfo", (User)o);
// return new ResponseEntity(200, "从Redis登录成功!", map);
// }
//}
// 2 检查登录数据是否存在空
if(StringUtils.isAnyBlank(ul.getUserAccount(), ul.getUserPwd())) {
logger.error("C UserServiceImpl M login() 数据存在空");
return new ResponseEntity(MessageEnum.NULL_BLANK_ERROR.getCode(),
MessageEnum.NULL_BLANK_ERROR.getMessage());
}
// 3 检查密码长度是否过短
if(ul.getUserAccount().length() < 8 || ul.getUserPwd().length() < 8) {
logger.error("C UserServiceImpl M login() 数据输入不规范");
return new ResponseEntity(MessageEnum.PWD_LENGTH_ERROR.getCode(),
MessageEnum.PWD_LENGTH_ERROR.getMessage());
}
// 4 账户不能包含特殊字符
String validPattern = "[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]";
Matcher matcher = Pattern.compile(validPattern).matcher(ul.getUserAccount());
if (matcher.find()) {
logger.error("C UserServiceImpl M login() 账户包含特殊字符");
return new ResponseEntity(MessageEnum.ACCOUNT_CHARACTERS_ERROR.getCode(),
MessageEnum.ACCOUNT_CHARACTERS_ERROR.getMessage());
}
// 5 判断这个用户是否存在
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userAccount", ul.getUserAccount());
User user = this.getOne(queryWrapper);
if(user == null) {
logger.error("C UserServiceImpl M login() 用户不存在");
return new ResponseEntity(MessageEnum.ACCOUNT_NOTEXSIT_ERROR.getCode(),
MessageEnum.ACCOUNT_NOTEXSIT_ERROR.getMessage());
}
// 6 核验密码是否正确
if(! user.getUserPwd().equals(MD5Util.getMD5Str(ConstantUtil.SALT + ul.getUserPwd()))){
logger.error("C UserServiceImpl M login() 校验密码");
return new ResponseEntity(MessageEnum.PWD_ERROR.getCode(),
MessageEnum.PWD_ERROR.getMessage());
}
// 7 将用户的登录信息放入Redis并返回用户信息给前端
String newTicket = UUIDUtil.getUUIDStr(); // 🎯随机字符串作为ticket
user = getSafetyUser(user); // 隐藏用户的敏感信息 密码
Cookie cookie = new Cookie("ticket", newTicket);
cookie.setPath("/"); // 设置cookie的有效路径
cookie.setMaxAge(3600 * 24); // 登录凭证的有效时间 单位为秒
response.addCookie(cookie); // 将登录凭证放到客户端cookie
// 将登录用户信息存入Redis
// redisTemplate.opsForValue().set(newTicket, user,24, TimeUnit.HOURS); // Redis存储12个小时
logger.info("C UserServiceImpl M login().. 所有校验均通过~");
// 8 返回最终的成功登录结果 并返回用户信息给前端
HashMap<String, Object> map = new HashMap<>();
map.put("currentUser", user);
return new ResponseEntity(200, "从MySQL登录成功!", map);
}
这个方法所在的类中会用到🎯两个属性 这两个属性等会儿需要在测试类中进行模拟 以@Spy的方式 类结构如下:
package bbm.com.service.impl;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
@Autowired
🎯private UserMapper um;
@Autowired
🎯private RedisTemplate redisTemplate;
/**
* 用户注册 流程如下
* ...
*/
public ResponseEntity register(UserRegister ur) {
logger.info("C UserServiceImpl M register()..");
...
}
...
}
🍭创建对应的测试类进行测试
只保留测试类的
setUp()
和login()
方法
package bbm.com.service.impl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class UserServiceImplTest {
@BeforeEach
void setUp() {
}
@Test
void login() {
}
}
🍭编写测试方法执行测试
package bbm.com.service.impl;
//@SpringBootTest()
class UserServiceImplTest {
@InjectMocks
@Spy
private UserServiceImpl userService;
@Mock
private UserMapper userMapper;
//@Mock
//private RedisTemplate<String, Object> redisTemplate;
@BeforeEach
void setUp() {
// 初始化模拟数据
MockitoAnnotations.openMocks(this);
}
@Test
void login() {
// 1 Redis登录成功的情形 暂未成功 🍭不过这个设定的数据在后面的测试中会用到
UserLogin userLogin = new UserLogin(); // 创建UserLogin对象
userLogin.setUserAccount("bigbigmeng03"); // 设置Redis中已经存在的用户信息
userLogin.setUserPwd("bigbigmeng03"); // 设置Redis中已经存在的用户信息
String ticket = "b616a99933fc4e6099f3ab3ee51432be";
// 模拟一个HttpServletResponse对象进行使用
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
// 打桩RedisTemplate
User user = new User(); // 创建一个模拟的User对象
//when(redisTemplate.opsForValue().get(ticket)).thenReturn(user); // 返回新的值
//Mockito.when(redisTemplate.opsForValue().get(ticket)).thenReturn(user);
// 断言成功则登录成功 第一个测试点通过
//Assertions.assertEquals(200, userService.login(userLogin, response, ticket).getCode());
ticket = ""; // 后面的测试的ticket都为空字符串
// 2 检查登录数据是否存在空
userLogin.setUserAccount(null);
Assertions.assertEquals(MessageEnum.NULL_BLANK_ERROR.getCode(), userService.login(userLogin, response, ticket).getCode());
userLogin.setUserAccount("");
Assertions.assertEquals(MessageEnum.NULL_BLANK_ERROR.getCode(), userService.login(userLogin, response, ticket).getCode());
// 3 检查密码长度是否过短
userLogin.setUserAccount("123456");
Assertions.assertEquals(MessageEnum.PWD_LENGTH_ERROR.getCode(), userService.login(userLogin, response, ticket).getCode());
userLogin.setUserAccount("bigbigmeng03");
userLogin.setUserPwd("123456");
Assertions.assertEquals(MessageEnum.PWD_LENGTH_ERROR.getCode(), userService.login(userLogin, response, ticket).getCode());
userLogin.setUserPwd("bigbigmeng03");
// 4 账户不能包含特殊字符
userLogin.setUserAccount("123456789+~");
Assertions.assertEquals(MessageEnum.ACCOUNT_CHARACTERS_ERROR.getCode(), userService.login(userLogin, response, ticket).getCode());
// 5 判断这个用户是否存在
userLogin.setUserAccount("bigbigmeng99"); // 账户不存在
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userAccount", "bigbigmeng99");
//when(userMapper.selectOne(queryWrapper)).thenCallRealMethod(); // 从数据库返回真正的用户密码 设置这里的userMapper是不起作用的
when(userService.getOne(queryWrapper)).thenReturn(null); // 返回新的值
Assertions.assertEquals(MessageEnum.ACCOUNT_NOTEXSIT_ERROR.getCode(), userService.login(userLogin, response, ticket).getCode());
// 6 核验密码是否正确
userLogin.setUserAccount("bigbigmeng03"); // 设置Redis中已经存在的用户信息
queryWrapper.eq("userAccount", "bigbigmeng03");
userLogin.setUserPwd("bigbigmeng03"); // 设置Redis中已经存在的用户信息 -> 密码不正确覆盖
user = new User();
user.setUserAccount(userLogin.getUserAccount());
user.setUserPwd(userLogin.getUserPwd());
Mockito.doReturn(user).when(userService).getOne(Mockito.any()); // 返回新的值
Assertions.assertEquals(MessageEnum.PWD_ERROR.getCode(), userService.login(userLogin, response, ticket).getCode());
// 7 将用户的登录信息放入Redis并返回用户信息给前端
user.setUserPwd(MD5Util.getMD5Str(ConstantUtil.SALT + "bigbigmeng03"));
when(userService.getOne(queryWrapper)).thenReturn(user); // 返回新的值
userLogin.setUserAccount("bigbigmeng03"); // 设置Redis中已经存在的用户信息
// 注意这里的密码是需要加密过的 ❗❗❗ 因为从数据库查的时候用的就是加密过的的密码
userLogin.setUserPwd("bigbigmeng03"); // 设置Redis中已经存在的用户信息
// 登录成功
Assertions.assertEquals(200, userService.login(userLogin, response, ticket).getCode());
}
}
执行结果:
🎄Mockito配合Spring使用
@SpringBootTest(classes = MockitoApp.class) // 启动类
public class UserServiceImplInSpringTest {
/**
* 不能配置@Spy: Argument passed to when() is not a mock!
*/
@Resource
@SpyBean
private UserServiceImpl userService;
@MockBean
private UserFeatureService userFeatureService;
@MockBean
private UserMapper userMapper;
@SpyBean
private DataSourceProperties dataSourceProperties;
@Test
public void testSelectById3() {
System.out.println(dataSourceProperties);
// 配置方法getById的返回值
UserDO ret = new UserDO();
ret.setId(1L);
ret.setUsername("BigBigMeng");
ret.setPhone("http://www.BigBigMeng.com");
doReturn(ret).when(userService).getById(1L);
// 配置userFeatureService.selectByUserId的返回值
List<UserFeatureDO> userFeatureDOList = new ArrayList<>();
UserFeatureDO userFeatureDO = new UserFeatureDO();
userFeatureDO.setId(88L);
userFeatureDO.setUserId(1L);
userFeatureDO.setFeatureValue("aaaa");
userFeatureDOList.add(userFeatureDO);
doReturn(userFeatureDOList).when(userFeatureService).selectByUserId(1L);
// 执行测试
UserVO userVO = userService.selectById(1L);
// 断言
Assertions.assertEquals(1,userVO.getFeatureValue().size());
}
@Test
public void testSelectById1() {
// 配置
UserVO userVO = userService.selectById(1L);
Assertions.assertNull(userVO);
}
}