Featured image of post Java工程师 [单元测试] Mockito的使用

Java工程师 [单元测试] Mockito的使用

🌏Java工程师 测试 Mockito的使用 🎯这篇文章用于记录 使用junit + Mockito对后端开发进行单元测试(注意测试路径是否覆盖全)

🎄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);
    }
}
Licensed under CC BY-NC-SA 4.0
最后更新于 2023年11月10日