Spring Boot Web Test

创建一个 Controller 类

package com.example.testingweb;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HomeController {
    @GetMapping("/")
    public String greeting() {
        return "Hello, World";
    }
}

如何测试这个 Controller

直接注入对应的 HomeController

@SpringBootTest
public class HomeControllerTest {
    @Autowired
    private HomeController homeController;

    @Test
    public void should_return_hello_world() {
        String expected = "Hello, World";

        String actual = homeController.greeting();

        assertEquals(expected, actual);
    }

}

启动一个随机端口进行测试

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;

import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class HttpRequestTest {

    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    public void should_return_hello_world() {
        String expected = "Hello, World";

        String actual = restTemplate.getForObject("http://localhost:" + port + "/", String.class);

        assertEquals(expected, actual);
    }

}

使用 WebEnvironment.RANDOM_PORT 在测试时启动一个真实的服务器,但使用随机端口。这种方式有以下优点:

  1. 避免端口冲突:在 CI/CD 环境中特别有用,因为多个测试可能同时运行
  2. 更接近生产环境:测试会通过实际的 HTTP 请求和响应
  3. 配置简单:使用 @LocalServerPort 自动注入随机生成的端口号
  4. 自动装配:Spring Boot 会自动提供配置好的 TestRestTemplate 实例,只需通过 @Autowired 注入即可

这种测试方式适合于需要验证完整 HTTP 请求-响应周期的场景。

不启动服务器进行测试

Spring Boot 提供了第三种测试方式,即在不启动实际服务器的情况下测试 Web 层。这种方法使用 Spring 的 MockMvc,通过模拟 HTTP 请求来测试控制器。测试会涵盖从 HTTP 请求到控制器的完整调用链路,但省去了启动服务器的开销。要使用这种方式,只需在测试类上添加 @AutoConfigureMockMvc 注解,并注入 MockMvc 实例即可。

这种方法的优点是:

  • 测试执行速度更快,因为不需要启动服务器
  • 可以直接测试 Spring MVC 的各层面功能
  • 提供了详细的请求处理结果断言支持
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
public class HomeControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void should_return_hello_world() throws Exception {
        String expected = "Hello, World";

        mockMvc.perform(get("/"))
            .andExpect(status().isOk())
            .andExpect(result -> {
                String actual = result.getResponse().getContentAsString();
                assertEquals(expected, actual);
            });
    }
}

仅测试 Web 层

在这个测试中,启动了完整的 Spring 应用程序上下文,但没有启动服务器。我们可以使用@WebMvcTest将测试范围缩小到仅 Web 层

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(HomeController.class)
public class HomeControllerMvcTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void test() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/"))
            .andExpect(status().isOk())
            .andExpect(content().string("Hello, World"));
    }
}

这种测试方式使用的断言方法与前面的 MockMvc 测试相似,但有一个重要区别:使用 @WebMvcTest 时,Spring Boot 只实例化 Web 层组件,而不是整个应用程序上下文。这种方式特别适用于大型应用,因为你可以通过 @WebMvcTest(HomeController.class) 这样的方式指定要测试的具体控制器,从而进一步提高测试效率。

注意如果 Controller 依赖其他的 bean,需要使用 MockBean,创建这个依赖的 bean,否则会启动失败。