【单元测试】Junit5 + Mockito

1 简介

1.1 单元测试的特点

配合断言使用(杜绝System.out)

可重复执行

不依赖环境

不会对数据产生影响

spring的上下文环境不是必须的

一般都需要配合mock类框架来实现

1.2 mock类框架使用场景

要进行测试的方法存在外部依赖(如db,redis,第三方接口调用等),为了能够专注于对该方法(单元)的逻辑进行测试,就希望能虚拟出外部依赖,避免外部依赖成为测试的阻塞项,一般都是测试service层即可。

1.3 常用mock类框架

mock类框架:用于mock外部依赖

1.3.1 mockito

官网: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,具体可以参考官网

原理:bytebuddy

1.3.2 easymock

1.3.3 powermock

官网:https://github.com/powermock/powermock

与mockito的版本支持关系:https://gitee.com/mirrors/powermock/wikis/Mockito#supported-versions

对mockito或easymock的增强

1.3.4 JMockit

2 mockito的单独使用

2.0 环境准备

2.0.1 maven依赖

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.13</version>
    </parent>

    <groupId>com.nico</groupId>
    <artifactId>mockito</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>mockito-demo</name>
    <description>mockito-demo</description>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <!--springboot相关-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <!--单元测试-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--spring-boot-starter-test中不包含junit4,需要单独引入-->
        <!--junit4-->
        <dependency>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
            <scope>test</scope>
        </dependency>
        <!--引入mockito,以下2个依赖在spring-boot-starter-test中已包含(4.0.0),所以不需要再单独引入-->
<!--        <dependency>-->
<!--            <groupId>org.mockito</groupId>-->
<!--            <artifactId>mockito-core</artifactId>-->
<!--        </dependency>-->
        <!--mockito配合junit5使用才需要的依赖,junit4不需要引入-->
<!--        <dependency>-->
<!--            <groupId>org.mockito</groupId>-->
<!--            <artifactId>mockito-junit-jupiter</artifactId>-->
<!--        </dependency>-->

        <!--mybatis相关-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.18</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.32</version>
        </dependency>

        <!--工具类-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-core</artifactId>
            <version>5.8.9</version>
        </dependency>

        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
            <scope>provided</scope>
        </dependency>

    </dependencies>

</project>

2.0.2 数据库

sql 复制代码
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 ('nico', '18844445555');

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');

2.1 mock对象与spy对象

|--------|----------|--------------|----------|-----------|
| | 方法插桩 | 方法不插桩 | 作用对象 | 最佳实践 |
| mock对象 | 执行插桩逻辑 | 返回mock对象的默认值 | 类、接口 | 被测试类或其他依赖 |
| spy对象 | 执行插桩逻辑 | 调用真实方法 | 类、接口 | 被测试类 |

2.2 初始化mock/spy对象的方式★

spy对象是另一种不同类型的mock对象;mock对象不是spy对象

2.3 参数匹配

2.3.1 测试代码

java 复制代码
package com.nico.mockito;

import com.nico.mockito.bean.req.UserUpdateReq;
import com.nico.mockito.bean.vo.UserVO;
import com.nico.mockito.service.UserService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;

import java.sql.SQLOutput;
import java.util.ArrayList;
import java.util.List;

import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyString;

/**
 * 参数匹配:通过方法签名(参数)来指定哪些方法调用需要被处理(插桩、verify验证)
 * @auther Nico
 * @since 2026/01/14 0014 19:31
 */
@ExtendWith(MockitoExtension.class)
public class ParamMatcherTest {

    @Mock
    private UserService mockUserService;

    @Test
    public void test4(){
        List<String> features = new ArrayList<>();
        mockUserService.add("Zelda", "111111", features);
        // 校验参数为 "Zelda", "111111", features 的add方法调用了1次
        Mockito.verify(mockUserService, Mockito.times(1)).add("Zelda", "111111", features);

        // 报错When using matchers, all arguments have to be provided by matchers.
//        Mockito.verify(mockUserService, Mockito.times(1)).add(ArgumentMatchers.anyString(), "111111", features);

        Mockito.verify(mockUserService, Mockito.times(1)).add(anyString(), anyString(), anyList());

    }

    /**
     * 拦截UserUpdateReq类型的任意实例对象
     * ArgumentMatchers.any拦截UserUpdateReq类型的任意对象
     * 除了any,还有anyXx(anyLong,anyString。。。),注意:它们都不包括null
     */
    @Test
    public void test3(){
        // 指定参数为UserUpdateReq类的任意实例对象时,调用mockUserService.modifyById方法,返回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);
        int result3 = mockUserService.modifyById(userUpdateReq1);
        System.out.println("result1 = " + result1);// result1 = 99

        // =======================================================================================
        UserUpdateReq userUpdateReq2 = new UserUpdateReq();
        userUpdateReq2.setId(2L);
        userUpdateReq2.setPhone("2L");
        int result2 = mockUserService.modifyById(userUpdateReq2);

        System.out.println("result2 = " + result2);// result2 = 99
    }

    /**
     * 测试插桩时的参数匹配,只拦截userUpdateReq1
     */
    @Test
    public void test2(){
        UserUpdateReq userUpdateReq1 = new UserUpdateReq();
        userUpdateReq1.setId(1L);
        userUpdateReq1.setPhone("1L");

        // 指定参数为userUpdateReq1时,调用mockUserService.modifyById返回99
        Mockito.doReturn(99).when(mockUserService).modifyById(userUpdateReq1);

        int result1 = mockUserService.modifyById(userUpdateReq1);
        int result3 = mockUserService.modifyById(userUpdateReq1);
        System.out.println("result1 = " + result1);// result1 = 99
        System.out.println("result3 = " + result3);// result3 = 99

        // =======================================================================================

        UserUpdateReq userUpdateReq2 = new UserUpdateReq();
        userUpdateReq2.setId(2L);
        userUpdateReq2.setPhone("2L");

        int result2 = mockUserService.modifyById(userUpdateReq2);
        System.out.println("result2 = " + result2);// result2 = 0
    }

    /**
     * 对于mock对象不会调用真实方法,直接返回mock对象的默认值
     * 默认值(int)、null(UserVO)、空集合(List)
     */
    @Test
    public void test1() {
        UserVO userVO = mockUserService.selectById(1L);
        System.out.println("userVO = " + userVO);// userVO = null
        
        UserUpdateReq userUpdateReq1 = new UserUpdateReq();
        int i = mockUserService.modifyById(userUpdateReq1);
        System.out.println("i = " + i);// i = 0
    }
}

2.4 方法插桩

指定调用某个方法时的行为(stubbing),达到相互隔离的目的

返回指定值

void返回值方法插桩

插桩的两种方式:

when(obj.someMethod()).thenXxx():

其中obj可以是mock对象。spy对象在没有插桩时是调用真实方法的,写在when中会导致先执行一次原方法,达不到mock的目的。因此spy一般使用下面的方法

doXxx().when(obj).someMethod():

其中obj可以是mock/spy对象,或对无返回值的方法进行插桩

抛异常

多次插桩

thenAnswer

执行真正的原始方法

verify的使用

2.4.1 指定返回值

java 复制代码
package com.nico.mockito;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.List;

import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;

/**
 * @auther Nico
 * @since 2026/01/15 0015 13:21
 */
@ExtendWith(MockitoExtension.class)
public class StubTest {

    @Mock
    private List<String> mockList;

    /**
     * 指定返回值
     */
    @Test
    public void test1(){
        // 方法一
        doReturn("zero").when(mockList).get(0);
        // Junit5里面自带的断言:如果返回值不相等,则本单元测试会失败
        Assertions.assertEquals("zero", mockList.get(0));

        // 方法二
        when(mockList.get(1)).thenReturn("one");
        Assertions.assertEquals("one", mockList.get(1));
    }
}

2.4.2 void返回值方法插桩

java 复制代码
@Mock
private List<String> mockList;

/**
 * void返回值方法插桩
 */
@Test
public void test2(){
    // 调用mockList.clear的时候什么也不做
    doNothing().when(mockList).clear();
    mockList.clear();
    // 验证调用了一次clear
    verify(mockList, times(1)).clear();
}

2.4.3 插桩的两种方式

java 复制代码
@Mock
private UserServiceImpl mockUserServiceImpl;
@Spy
private UserServiceImpl spyUserServiceImpl;

/**
 * 插桩的两种方式
 */
@Test
public void test3(){
    when(mockUserServiceImpl.getNumber()).thenReturn(99);
    System.out.println("mockUserServiceImpl.getNumber() = " + mockUserServiceImpl.getNumber());// mockUserServiceImpl.getNumber() = 99

    when(spyUserServiceImpl.getNumber()).thenReturn(99);// 这里的spy对象,就会先执行真实方法一次

    /**
     * getNumber
     * spyUserServiceImpl.getNumber() = 99
     * spy对象在没有插桩时是调用真实方法的,写在when中会导致先执行一次原方法,达不到mock的目的,
     * 需要使用doXxx().when(obj).someMethod()
     */
    System.out.println("spyUserServiceImpl.getNumber() = " + spyUserServiceImpl.getNumber());

    doReturn(1000).when(spyUserServiceImpl).getNumber();
    System.out.println("spyUserServiceImpl.getNumber() = " + spyUserServiceImpl.getNumber());// spyUserServiceImpl.getNumber() = 1000
}

2.4.4 抛出异常

java 复制代码
@Mock
private List<String> mockList;

/**
 * 抛出异常
 */
@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);
    }
}

2.4.5 多次插桩

java 复制代码
@Mock
private List<String> mockList;

/**
 * 多次插桩
 */
@Test
public void test5() {
    // 第一次调用返回1,第二次调用返回2,第三次及之后的调用都返回3
//        when(mockList.size()).thenReturn(1).thenReturn(2).thenReturn(3);
    // 可简写为:
    when(mockList.size()).thenReturn(1, 2, 3);
    Assertions.assertEquals(1, mockList.size());
    Assertions.assertEquals(2, mockList.size());
    Assertions.assertEquals(3, mockList.size());
    Assertions.assertEquals(3, mockList.size());
}

2.4.6 thenAnswer指定插桩逻辑

java 复制代码
@Mock
private List<String> mockList;

/**
 * thenAnswer 实现指定逻辑的插桩
 */
@Test
public void test6(){
    when(mockList.get(anyInt())).thenAnswer(new Answer<String>() {
        /**
         * 泛型表示要插桩方法的返回值类型
         */
        @Override
        public String answer(InvocationOnMock invocation) throws Throwable {
            // getArgument表示获取插桩方法(此处就是List.get)的第几个参数值
            Integer argument = invocation.getArgument(0, Integer.class);
            return String.valueOf(argument * 100);
        }
    });

    String result = mockList.get(3);
    Assertions.assertEquals("300", result);
}

2.4.7 执行真正的原始方法

java 复制代码
@Mock
private UserServiceImpl mockUserServiceImpl;
@Spy
private UserServiceImpl spyUserServiceImpl;

/**
 * 执行真正的原始方法
 */
@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);
}

2.4.8 verify的使用

java 复制代码
@Mock
private List<String> mockList;

/**
 * 测试verify
 */
@Test
public void test8() {
    mockList.add("one");
    Assertions.assertEquals(0, mockList.size());// true:调用mock对象的写操作方法是没效果的
    mockList.clear();
    // 验证调用过1次add方法,且参数必须是one
    verify(mockList).add("one");// 指定要验证的方法和参数,这里不是调用,也不会产生调用效果
    verify(mockList, times(1)).clear();// 等价于上面verify(mockedList)

    // 校验没有调用的两种方式
    verify(mockList, times(0)).get(1);
    verify(mockList, never()).get(1);

    // 校验最少或最多调用了多少次
    verify(mockList, atLeast(1)).clear();
    verify(mockList, atMost(1)).clear();
}

2.5 @InjectMocks注解的使用

作用:若@InjectMocks声明的变量需要用到mock/spy对象,mockito会自动使用当前类里的mock或spy成员进行按类型或名字的注入

原理:构造器注入、setter注入、字段反射注入(按构造器、setter、字段反射的顺序判断选择哪种注入,如果构造器中有参数,则优先用构造器注入,后面依次类推)

spy对象是另一种不同类型的mock对象;mock对象不是spy对象

@InjectMocks 一般和 @Spy搭配使用,如果只有@InjectMocks的话,注入的就是一个普通的实例对象,一个未经过Mockito处理的普通对象

如果加上@Spy后,注入的就是一个经过Mockito处理的对象

java 复制代码
package com.nico.mockito;

import com.nico.mockito.service.UserFeatureService;
import com.nico.mockito.service.impl.UserServiceImpl;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.List;

/**
 * @auther Nico
 * @since 2026/01/15 0015 22:59
 */
@ExtendWith(MockitoExtension.class)
public class InjectMocksTest {

    /**
     * 1.被@InjectMocks标注的属性必须是实现类,因为mockito会创建对应的实例对象,默认创建的对象就是
     * 未经过mockito处理的普通对象,因此常配合@Spy注解使其变为默认调用真实方法的mock对象
     * 2.mockito会使用spy对象或mock对象 注入到被@InjectMocks标注的实例对象中(如在userService中注入userFeatureService属性)
     */
    @InjectMocks
    @Spy
    private UserServiceImpl userService;

    @Mock
    private UserFeatureService userFeatureService;//UserServiceImpl中有UserFeatureService属性,就会注入

    @Mock
    private List<String> mockList;//UserServiceImpl中没有List<String>属性,就不会注入

    @Test
    public void test1() {
        int number = userService.getNumber();
        Assertions.assertEquals(0, number);
    }
}

2.6 断言工具

hamcrest:junit4中引入的第三方断言库,junit5中被移除,从1.3版本后,坐标由org.hamcrest:hamcrest变为org.hamcrest:hamcrest(比较老了,用得少)

AssertJ:常用的断言库

Junit4 原生断言

Junit5 原生断言

java 复制代码
package com.nico.mockito;

import org.assertj.core.api.Assertions;
import org.hamcrest.MatcherAssert;
import org.hamcrest.core.IsEqual;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.List;

import static org.mockito.Mockito.when;

/**
 * @auther Nico
 * @since 2026/01/16 0016 19:51
 */
@ExtendWith(MockitoExtension.class)
public class AssertTest {
    @Mock
    private List<String> mockList;

    @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());
    }
}

2.8 Assertions(断言)和verify(验证)的区别

核心区别

方面 Assertions(断言) verify(验证)
关注点 验证业务结果是否正确 验证方法调用是否正确
测试对象 业务数据、返回值、状态 Mock对象的交互行为
使用场景 所有测试(单元、集成) Mockito测试(主要是单元测试)
底层原理 断言库(JUnit、AssertJ) Mockito框架的调用追踪

总结

  • Assertions :验证业务逻辑的正确性(结果对不对)

  • verify :验证代码的交互行为(方法调没调用、调了几次)

  • 两者互补:好的单元测试应该同时包含两者

简单记忆

  • Assertions 回答:"结果正确吗?"

  • verify 回答:"方法调用正确吗?"

在系统测试中,应该同时使用两者来确保代码的正确性。

3 实战讲解

java 复制代码
package com.nico.mockito;

import com.nico.mockito.bean.entity.UserDO;
import com.nico.mockito.bean.entity.UserFeatureDO;
import com.nico.mockito.bean.vo.UserVO;
import com.nico.mockito.mapper.UserFeatureMapper;
import com.nico.mockito.mapper.UserMapper;
import com.nico.mockito.service.UserFeatureService;
import com.nico.mockito.service.impl.UserServiceImpl;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.ArrayList;
import java.util.List;

import static org.mockito.Mockito.doReturn;

/**
 * 实战讲解
 * @auther Nico
 * @since 2026/01/16 0016 20:54
 */
@ExtendWith(MockitoExtension.class)
public class UserServiceImplTest {

    @InjectMocks
    @Spy
    private UserServiceImpl userService;

    @Mock
    private UserFeatureService userFeatureService;

    @Mock
    private UserMapper userMapper;

    @Test
    public void testSelectById3() {
        // 配置方法getById的返回值
        UserDO userDO = new UserDO();
        userDO.setId(1L);
        userDO.setUsername("Nico");
        userDO.setPhone("13344445555");
        doReturn(userDO).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 testSelectById2() {
        // 配置方法getById的返回值
        UserDO userDO = new UserDO();
        userDO.setId(1L);
        userDO.setUsername("Nico");
        userDO.setPhone("13344445555");
        doReturn(userDO).when(userService).getById(1L);

        // 执行测试
        UserVO userVO = userService.selectById(1L);
        // 断言
        Assertions.assertNotNull(userVO);
    }

    @Test
    public void testSelectById1() {
        // 配置
        doReturn(userMapper).when(userService).getBaseMapper();
        UserVO userVO = userService.selectById(1L);
        Assertions.assertNull(userVO);
    }

}

4 Mockito在SpringBoot环境使用(不推荐)

生成的对象受Spring管理,相当于自动替换对应类型bean的注入

4.1 @MockBean

类似@Mock

用于通过类型或名字替换Spring容器中已经存在的bean,从而达到对这些bean进行mock的目的

4.2 @SpyBean

作用类似于@Spy

用于通过类型或名字包装Spring容器中已经存在的bean,当需要mock被测试类的某些方法时可以使用

java 复制代码
package com.nico.mockito.spring;

import com.nico.mockito.MockitoDemoApplication;
import com.nico.mockito.bean.entity.UserDO;
import com.nico.mockito.bean.entity.UserFeatureDO;
import com.nico.mockito.bean.vo.UserVO;
import com.nico.mockito.mapper.UserMapper;
import com.nico.mockito.service.UserFeatureService;
import com.nico.mockito.service.impl.UserServiceImpl;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.Spy;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

import static org.mockito.Mockito.doReturn;

/**
 * mockito配合spring使用
 * @auther Nico
 * @since 2026/01/16 0016 20:54
 */
@SpringBootTest(classes = MockitoDemoApplication.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 userDO = new UserDO();
        userDO.setId(1L);
        userDO.setUsername("Nico");
        userDO.setPhone("13344445555");
        doReturn(userDO).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 testSelectById2() {
        // 配置方法getById的返回值
        UserDO userDO = new UserDO();
        userDO.setId(1L);
        userDO.setUsername("Nico");
        userDO.setPhone("13344445555");
        doReturn(userDO).when(userService).getById(1L);

        // 执行测试
        UserVO userVO = userService.selectById(1L);
        // 断言
        Assertions.assertNotNull(userVO);
    }

    @Test
    public void testSelectById1() {
        // 配置
        UserVO userVO = userService.selectById(1L);
        Assertions.assertNull(userVO);
    }

}

5 使用插件Squaretest快速编写单元测试

idea 插件:Squaretest

插件设置:

java 复制代码
package com.nico.mockito.service.impl;

import com.nico.mockito.bean.entity.UserDO;
import com.nico.mockito.bean.entity.UserFeatureDO;
import com.nico.mockito.bean.req.UserUpdateReq;
import com.nico.mockito.bean.vo.UserVO;
import com.nico.mockito.service.UserFeatureService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;

/**
 * squaretest使用:
 * 1.先运行一下,根据报错进行修改
 * 2.被测试类一般要加上@Spy(因为可能需要对某些方法进行插桩)
 * 3.依次检查各个方法,根据错误提示进行修改(对一些方法进行插桩、修改返回值、修改断言......)
 */
@ExtendWith(MockitoExtension.class)
class UserServiceImplTest {

    @Mock
    private UserFeatureService mockUserFeatureService;

    @InjectMocks
    @Spy //这里不能是@Mock,必须是@Spy @Mock对象所有方法都是假的。
    // 这里我们测试UserServiceImpl,调用真实方法,方法中的方法进行插桩,定制返回值
    private UserServiceImpl userServiceImplUnderTest;

    @Test
    void testSelectById() {
        // Setup

        // Configure UserFeatureService.getById(...).
        UserDO existedEntity = new UserDO();
        existedEntity.setId(0L);
        existedEntity.setUsername("username");
        existedEntity.setPhone("phone");
        doReturn(existedEntity).when(userServiceImplUnderTest).getById(0L);

        // Configure UserFeatureService.selectByUserId(...).
        final UserFeatureDO userFeatureDO = new UserFeatureDO();
        userFeatureDO.setId(0L);
        userFeatureDO.setUserId(0L);
        userFeatureDO.setFeatureValue("featureValue");
        final List<UserFeatureDO> userFeatureDOList = Arrays.asList(userFeatureDO);
        when(mockUserFeatureService.selectByUserId(0L)).thenReturn(userFeatureDOList);

        // Run the test
        final UserVO result = userServiceImplUnderTest.selectById(0L);

        // Verify the results
        assertThat(result.getFeatureValue().size()).isEqualTo(1);
    }

    @Test
    void testSelectById_UserFeatureServiceReturnsNoItems() {
        // Setup

        // Configure UserFeatureService.getById(...).
        UserDO existedEntity = new UserDO();
        existedEntity.setId(0L);
        existedEntity.setUsername("username");
        existedEntity.setPhone("phone");
        doReturn(existedEntity).when(userServiceImplUnderTest).getById(0L);

        when(mockUserFeatureService.selectByUserId(0L)).thenReturn(Collections.emptyList());

        // Run the test
        final UserVO result = userServiceImplUnderTest.selectById(0L);

        // Verify the results
        assertThat(result.getFeatureValue()).isNull();
    }

    @Test
    void testAdd() {
        // Setup
        when(mockUserFeatureService.saveBatch(anyList())).thenReturn(true);
        // config
        doReturn(true).when(userServiceImplUnderTest).save(any(UserDO.class));
        // Run the test
//        userServiceImplUnderTest.add("username", "phone", Arrays.asList("value"));

        // 因为userServiceImplUnderTest.add方法是void返回值,所以可以断言它不抛异常
        Assertions.assertDoesNotThrow(() -> userServiceImplUnderTest.add("username", "phone", Arrays.asList("value")));

        // Verify the results
        verify(mockUserFeatureService).saveBatch(anyList());
    }

    @Test
    void testModifyById() {
        // Setup
        final UserUpdateReq userUpdateReq = new UserUpdateReq();
        userUpdateReq.setId(0L);
        userUpdateReq.setPhone("phone");

        // config
        doReturn(true).when(userServiceImplUnderTest).updateById(any(UserDO.class));
        // Run the test
        final int result = userServiceImplUnderTest.modifyById(userUpdateReq);

        // Verify the results
        assertThat(result).isEqualTo(1);
    }

    @Test
    void testGetNumber() {
        assertThat(userServiceImplUnderTest.getNumber()).isEqualTo(0);
    }
}
相关推荐
程序员雷叔13 小时前
在postman设置请求里带动态token,看看这两种方法!
selenium·测试工具·单元测试·测试用例·pytest·lua·postman
IMPYLH1 天前
Lua 的 String(字符串) 模块
开发语言·笔记·单元测试·lua
卓码软件测评2 天前
第三方软件确认测试机构【性能测试中内存泄漏的迹象:如何利用LoadRunner监控和发现 】
测试工具·ci/cd·性能优化·单元测试·测试用例
移幻漂流2 天前
Lua关键字全解析:从基础到精通的语义指南
junit·单元测试·lua
databook4 天前
【总结整理】软件测试的反模式
单元测试·测试
l***21785 天前
Spring Boot 整合 log4j2 日志配置教程
spring boot·单元测试·log4j
川石课堂软件测试5 天前
Android和iOS APP平台测试的区别
android·数据库·ios·oracle·单元测试·测试用例·cocoa
大熊猫侯佩5 天前
Swift 6.2 列传(第十七篇):钟灵的“雷电蟒”与测试附件
单元测试·swift·apple