首页 / Spring Boot 入门教程 / 单元测试:JUnit 5 与 Mockito

Spring Boot 入门教程

单元测试:JUnit 5 与 Mockito

本教程共 48 篇 · 第 39 篇 · 更新于 2026-08-13 · 约 4 分钟阅读

Spring Boot单元测试JUnit 5MockitoAssertJ@SpringBootTest测试

本节目标:会用 @SpringBootTest 启动应用上下文,用 Mockito 模拟依赖,用 AssertJ 写断言,跑通第一个单元测试。

测试为什么值得写

没写过测试的代码,改起来像走钢丝。改一个方法,不知道哪里会炸。

测试就是安全网。改了代码跑一遍测试,绿的说明没改坏,红的说明踩到雷了。Spring Boot 把测试支持做得很全,加一个依赖就能开写。

加依赖:spring-boot-starter-test

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

一个 starter 打包了全套测试工具:

  • JUnit 5(Jupiter):测试框架本体。
  • AssertJ:流式断言,assertThat(...) 一串链下去。
  • Hamcrest:老牌匹配器,现在用得少。
  • Mockito:模拟对象。
  • JSONassert / JsonPath:断言 JSON 用。

版本全部由 Spring Boot 统一管理,不用写版本号。

第一个测试:@SpringBootTest

测试类放在 src/test/java 下,包名和主类一致:

package com.example.demo;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class DemoApplicationTests {

    @Test
    void contextLoads() {
        // 应用上下文能启动,说明 Bean 装配没问题
    }
}

@SpringBootTest 会通过 SpringApplication 创建完整的应用上下文。它自动从测试类所在包向上找 @SpringBootApplication 主类,不用手动指定。

Note

默认情况下 @SpringBootTest 不启动服务器。它加载的是 mock 的 Web 环境,专门配合 MockMvc 用。想启动真实端口,用 webEnvironment 属性,下一节会讲。

跑测试用 Maven:

mvn test

看到 Tests run: 1, Failures: 0 就是全绿。

断言:AssertJ 是主角

JUnit 5 自带 Assertions.assertEquals,但 AssertJ 的链式写法更顺:

import org.junit.jupiter.api.Test;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

class DemoAssertionTests {

    @Test
    void assertjStyle() {
        String name = "Spring Boot";
        List<String> tags = List.of("测试", "打包", "部署");

        assertThat(name).startsWith("Spring").endsWith("Boot");
        assertThat(tags).hasSize(3).contains("测试");
        assertThat(tags).allMatch(tag -> tag.length() > 1);
    }
}

失败时 AssertJ 会打出详细的对比信息:期望什么、实际是什么。排错体验比 assertEquals 好一截。

生命周期注解

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class DemoLifecycleTests {

    private String message;

    @BeforeEach
    void setUp() {
        message = "ready"; // 每个测试方法执行前都会跑
    }

    @Test
    @DisplayName("准备阶段生效")
    void messageIsReady() {
        assertThat(message).isEqualTo("ready");
    }
}

常用的一套:@BeforeEach 准备数据,@AfterEach 清理现场,@DisplayName 给测试起中文名。测试名写清楚,失败时一眼看懂哪里不对。

Mockito:模拟外部依赖

单元测试要快、要稳定。可 Service 里常依赖数据库、第三方接口,一跑就牵连一堆。Mockito 的解法是造假:造一个「替身」对象,指定它返回什么,测试只关注当前类的逻辑。

纯单元测试:@Mock + @InjectMocks

不启动 Spring,纯 JUnit 5 风格:

package com.example.demo.service;

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

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;

@ExtendWith(MockitoExtension.class)
class GreetingServiceTest {

    @Mock
    private UserRepository userRepository;   // 替身

    @InjectMocks
    private GreetingService greetingService; // 把替身注入进来

    @Test
    void greetReturnsUserName() {
        given(userRepository.findNameById(1L)).willReturn("小明");

        String result = greetingService.greet(1L);

        assertThat(result).isEqualTo("你好,小明");
        verify(userRepository).findNameById(1L); // 确认真的调用了
    }
}

三个关键点:

  • @Mock:创建替身对象,方法默认返回 null / 空集合。
  • @InjectMocks:把替身按类型注入被测对象。
  • given(...).willReturn(...):指定替身行为,这是 BDD 风格,比 when(...).thenReturn(...) 读起来更像「安排」。

Mockito 的版本由 spring-boot-starter-test 管理,不需要单独声明 mockito-core

Tip

初学者常忘 @ExtendWith(MockitoExtension.class)@Mock 注解不生效,对象全是 null。跑起来报空指针,先查这个。

在 Spring 测试里换 Bean:@MockitoBean

@SpringBootTest 场景下,想替换容器里的真实 Bean,用 @MockitoBean。这是 Spring Framework 提供的注解,Spring Boot 4.x 的主推写法,@MockBean 在 4.x 已移除,必须改用 @MockitoBean

package com.example.demo.service;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.bean.override.mockito.MockitoBean;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;

@SpringBootTest
class OrderServiceIntegrationTest {

    @Autowired
    private OrderService orderService;

    @MockitoBean
    private PaymentClient paymentClient; // 容器里的真实 Bean 被替换成替身

    @Test
    void orderTotalIncludesShipping() {
        given(paymentClient.charge(100)).willReturn(true);

        boolean paid = orderService.pay(100);

        assertThat(paid).isTrue();
    }
}

@MockitoBean 的效果:测试期间,容器中该类型的 Bean 换成 Mockito 替身,测试结束自动恢复。远程服务、短信通道这类不稳定依赖,一律用替身。

@TestConfiguration:给测试加料

有时候测试需要额外的配置 Bean,又不想污染主配置。用嵌套的 @TestConfiguration

package com.example.demo.service;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;

@SpringBootTest
class DemoWithExtraConfigTests {

    @TestConfiguration(proxyBeanMethods = false)
    static class ExtraConfig {

        @Bean
        String extraGreeting() {
            return "测试专用问候";
        }
    }

    @Test
    void testSomething() {
        // 上下文里多了一个 extraGreeting Bean
    }
}

区别要分清:普通嵌套 @Configuration替换主配置,@TestConfiguration追加。它不会被组件扫描误抓,只在显式导入的测试里生效。

抓控制台输出:OutputCaptureExtension

要断言日志、System.out 输出,用 OutputCaptureExtension

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;

import static org.assertj.core.api.Assertions.assertThat;

@ExtendWith(OutputCaptureExtension.class)
class DemoOutputTests {

    @Test
    void capturesOutput(CapturedOutput output) {
        System.out.println("Hello World!");
        assertThat(output).contains("World");
    }
}

上下文缓存:跑得快的小秘密

Spring 的测试框架会缓存应用上下文。多个测试类配置相同,上下文只启动一次,后面直接复用。所以别在测试里随意改配置属性——配置一变,缓存失效,又要重新启动。

Warning

@SpringBootTest 是「重量级」测试,一个上下文启动可能要几秒。单元测试写多了,优先用切片测试(@WebMvcTest@DataJpaTest)和纯 Mockito 测试,只加载需要的部分。下一节讲 Web 层切片。

小结

  • spring-boot-starter-test 一个依赖带齐 JUnit 5、AssertJ、Mockito。
  • 断言首选 AssertJ,链式、报错信息友好。
  • 纯逻辑用 @Mock + @InjectMocks;要 Spring 容器用 @SpringBootTest + @MockitoBean
  • 测试命名用 @DisplayName 写人话,失败时才知道测的是什么。