第35课:E2E 端到端接口测试

E2E 测试是最接近生产环境的测试方式,它启动完整的 Spring Boot 应用,不 Mock 任何层,真实调用 Controller → Service → Repository → Database。这是验证系统整体行为的最可靠手段,也是成本最高的测试方式。


@SpringBootTest 启动完整环境

KP-139 | @SpringBootTest 的完整环境启动

@SpringBootTest 会启动完整的 ApplicationContext,加载所有自动配置、所有 Bean、完整 Web 容器。

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class SignInE2ETest {
 
    @Autowired
    private WebTestClient webTestClient;
 
    // ...
}
webEnvironment说明
RANDOM_PORT启动嵌入式容器,随机端口(推荐)
DEFINED_PORT使用配置的端口
MOCK不启动容器,使用 MockMvc(不是真正的 E2E)
NONE不启动 Web 环境

E2E 测试不能使用 Mock

KP-140 | E2E 测试不能使用 Mock

E2E 测试的核心理念是:不 Mock 任何层。所有调用都必须真实地经过整个调用链。

graph LR
    subgraph ”E2E 测试调用链”
        A[”WebTestClient<br/>(真实 HTTP 请求)”] --> B[”Controller<br/>(真实)”]
        B --> C[”Service<br/>(真实)”]
        C --> D[”Repository<br/>(真实)”]
        D --> E[”Database<br/>(真实 或 TestContainers)”]
    end

    subgraph ”切片测试调用链(对比)”
        F[”MockMvc<br/>(模拟 HTTP)”] --> G[”Controller<br/>(真实)”]
        G --> H[”Service<br/>(@MockBean)”]
    end

    style E fill:#4CAF50,color:#fff
    style D fill:#4CAF50,color:#fff
    style C fill:#4CAF50,color:#fff
    style B fill:#4CAF50,color:#fff
    style A fill:#4CAF50,color:#fff
    style H fill:#FF9800,color:#fff
    style G fill:#FF9800,color:#fff
    style F fill:#FF9800,color:#fff

E2E 测试中绝对不能使用 @MockBean@Mock。一旦 Mock 了某个层,就不再是"端到端"测试了。


WebTestClient 使用

KP-142 | WebTestClient 的使用

WebTestClient 是 Spring WebFlux 提供的 HTTP 客户端,在测试中它既可以配合 MockMvc 使用,也可以发送真实的 HTTP 请求。在 @SpringBootTest(webEnvironment = RANDOM_PORT) 模式下,它发送的是真实 HTTP 请求。

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class SignInE2ETest {
 
    @Autowired
    private WebTestClient webTestClient;
 
    @Test
    void should_signin_successfully() {
        // given
        var request = new SignInRequest("admin", "password");
 
        // when & then
        webTestClient.post()
            .uri("/api/signin")
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue(request)
            .exchange()
            .expectStatus().isOk()
            .expectBody()
            .jsonPath("$.token").isNotEmpty()
            .jsonPath("$.username").isEqualTo("admin");
    }
 
    @Test
    void should_return_401_when_invalid_password() {
        // given
        var request = new SignInRequest("admin", "wrong-password");
 
        // when & then
        webTestClient.post()
            .uri("/api/signin")
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue(request)
            .exchange()
            .expectStatus().isUnauthorized();
    }
}

WebTestClient 与 MockMvc 的对比

特性MockMvcWebTestClient
HTTP 请求模拟(DispatcherServlet 层面)真实(经过网络栈)
Web 容器不启动启动
适用场景@WebMvcTest@SpringBootTest
响应验证andExpect()expectStatus() / expectBody()

@AfterEach 清理测试数据

KP-141 | E2E 测试的数据清理(@AfterEach)

E2E 测试操作的是真实数据库,每次测试后必须清理数据,否则会污染后续测试。

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class SignInE2ETest {
 
    @Autowired
    private WebTestClient webTestClient;
 
    @Autowired
    private UserRepository userRepository;
 
    @AfterEach
    void cleanUp() {
        userRepository.deleteAll();
    }
 
    @Test
    void should_signin_successfully() {
        // ...
    }
 
    @Test
    void should_signin_and_signout() {
        // ...
    }
}

虽然切片测试(如 @DataJpaTest)会自动回滚事务,但 E2E 测试中 @SpringBootTest 不会自动回滚。你必须手动清理数据。


组合接口调用

KP-143 | E2E 测试组合接口调用(如先登录再登出)

E2E 测试的一个重要价值是验证多个接口组合的场景:

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class SignFlowE2ETest {
 
    @Autowired
    private WebTestClient webTestClient;
 
    @Autowired
    private UserRepository userRepository;
 
    @AfterEach
    void cleanUp() {
        userRepository.deleteAll();
    }
 
    @Test
    void should_signin_then_signout() {
        // Step 1: 先注册用户(如果需要)
        var signUpRequest = new SignUpRequest("alice", "password123");
        webTestClient.post()
            .uri("/api/signup")
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue(signUpRequest)
            .exchange()
            .expectStatus().isCreated();
 
        // Step 2: 登录获取 token
        var signInRequest = new SignInRequest("alice", "password123");
        var result = webTestClient.post()
            .uri("/api/signin")
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue(signInRequest)
            .exchange()
            .expectStatus().isOk()
            .expectBody()
            .jsonPath("$.token").isNotEmpty()
            .returnResult();
 
        var token = result.getResponseBody();  // 使用 token
 
        // Step 3: 登出(携带 token)
        webTestClient.post()
            .uri("/api/signout")
            .header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
            .exchange()
            .expectStatus().isOk();
    }
}

@SpringBootTest 的三种成本

KP-144 | @SpringBootTest 的三种成本:启动慢、配置高、数据清理

成本维度说明影响
启动慢需要加载所有 Bean、所有自动配置、启动嵌入式 Web 容器一次启动 10~30 秒
配置高需要完整的数据库连接、消息队列、外部服务等环境配置复杂
数据清理每个测试修改的真实数据必须手动清理代码复杂度增加

什么时候应该用 E2E 测试?

graph TD
    A[”需求变更 / 新功能开发”] --> B{”涉及多层协作?”}
    B -->|”是”| C{”变更影响核心流程?”}
    B -->|”否”| D[”使用切片测试”]
    C -->|”是,核心流程”| E[”编写 E2E 测试”]
    C -->|”否,边缘逻辑”| F[”切片测试 + 集成测试”]

    style E fill:#f44336,color:#fff
    style D fill:#4CAF50,color:#fff
    style F fill:#FF9800,color:#fff

推荐使用 E2E 的场景

  • 核心业务流程(如用户注册 → 登录 → 下单 → 支付)
  • 跨层协作逻辑(Controller 调用 Service 调用 Repository 的完整链路)
  • 数据一致性验证(事务、回滚、约束等数据库行为)
  • 部署前的冒烟测试(验证所有组件正确集成)

不推荐使用 E2E 的场景


测试策略总结

测试类型注解启动速度Mock?适用场景
纯单元测试无(JUnit + Mockito)毫秒级Mock 所有依赖Service 业务逻辑
MVC 切片@WebMvcTest秒级Mock Service 层Controller 端点映射
Security 切片@WebMvcTest + @WithMockUser秒级Mock 认证 + ServiceWeb 功能测试
JSON 切片@JsonTest秒级不 Mock序列化/反序列化
Repository 切片@DataJpaTest秒级不 Mock数据库访问
E2E@SpringBootTest十秒级不 Mock核心业务流程

前情回顾

下一步