时间:2025-08-26 17:09
人气:
作者:admin
在 Spring Boot 3.x 中为控制器编写集成测试,主要是通过 @SpringBootTest 注解加载完整的应用上下文,并利用 MockMvc 或 TestRestTemplate 来模拟 HTTP 请求并验证响应。下面我将为你提供一个清晰的指南和代码示例。
在Spring Boot项目中,测试通常分为单元测试和集成测试。以下是区分这两种测试的一些指导原则:
@SpringBootTest
public class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
public void testGetUserById() {
// Arrange
User user = new User(1, "John Doe");
when(userRepository.findById(1)).thenReturn(Optional.of(user));
// Act
User result = userService.getUserById(1);
// Assert
assertEquals("John Doe", result.getName());
}
}
@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testGetUser() throws Exception {
mockMvc.perform(get("/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("John Doe"));
}
}
测试目标:
使用的工具:
项目结构:
src/test/java目录中创建不同的包,例如unit和integration,分别存放单元测试和集成测试。命名约定:
UserServiceTest(单元测试)和UserControllerIntegrationTest(集成测试)。确保你的 pom.xml 中包含 Spring Boot Test starter 依赖,它通常已经包含了 JUnit Jupiter、Mockito、AssertJ 等测试所需的库。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
假设你有一个简单的 REST 控制器 ExampleController:
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class ExampleController {
@GetMapping("/greet")
public String greet(@RequestParam(required = false, defaultValue = "World") String name) {
return "Hello, " + name + "!";
}
@PostMapping("/users")
public User createUser(@RequestBody User user) {
// 假设这是一个创建用户并返回创建后信息的服务
return userService.save(user); // userService 需要通过依赖注入
}
}
创建一个集成测试类,使用 @SpringBootTest 和 @AutoConfigureMockMvc 来配置测试环境。
这种方法不会启动真正的服务器,而是模拟 Servlet 环境,测试速度较快。
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.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import com.fasterxml.jackson.databind.ObjectMapper;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
// 使用 @SpringBootTest 加载完整的应用程序上下文
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) // WebEnvironment.MOCK 是默认值,可省略
@AutoConfigureMockMvc // 自动配置 MockMvc
public class ExampleControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper; // Jackson 库的 ObjectMapper,用于对象与 JSON 转换
@Test
public void testGreetEndpoint() throws Exception {
mockMvc.perform(get("/api/greet") // 发起 GET 请求
.param("name", "Spring")) // 添加请求参数
.andExpect(status().isOk()) // 断言状态码为 200
.andExpect(content().string("Hello, Spring!")); // 断言响应内容
}
@Test
public void testGreetEndpointWithDefault() throws Exception {
mockMvc.perform(get("/api/greet"))
.andExpect(status().isOk())
.andExpect(content().string("Hello, World!"));
}
@Test
public void testCreateUser() throws Exception {
User newUser = new User("Alice", "alice@example.com");
// 将 User 对象转换为 JSON 字符串
String userJson = objectMapper.writeValueAsString(newUser);
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON) // 设置请求内容类型
.content(userJson)) // 设置请求体 JSON 内容
.andExpect(status().isCreated()) // 断言状态码为 201(假设创建成功返回 201)
.andExpect(jsonPath("$.name").value("Alice")) // 使用 JsonPath 断言返回的 JSON 字段值
.andExpect(jsonPath("$.email").value("alice@example.com"));
}
}
这种方法会启动一个嵌入式的真实服务器(如 Tomcat),测试更接近于生产环境。
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.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import static org.assertj.core.api.Assertions.assertThat;
// 使用 RANDOM_PORT 启动一个嵌入式服务器并监听随机端口,避免端口冲突
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ExampleControllerWithServerTest {
@Autowired
private TestRestTemplate restTemplate; // 注入 TestRestTemplate
@Test
public void testGreetEndpoint() {
// 使用 TestRestTemplate 发起请求
ResponseEntity<String> response = restTemplate.getForEntity("/api/greet?name=Spring", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isEqualTo("Hello, Spring!");
}
// 也可以测试 POST 请求
@Test
public void testCreateUser() {
User newUser = new User("Bob", "bob@example.com");
ResponseEntity<User> response = restTemplate.postForEntity("/api/users", newUser, User.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
assertThat(response.getBody()).isNotNull();
assertThat(response.getBody().getName()).isEqualTo("Bob");
assertThat(response.getBody().getEmail()).isEqualTo("bob@example.com");
}
}
下表总结了集成测试中常用的注解及其用途:
| 注解 | 说明 | 适用场景 |
|---|---|---|
@SpringBootTest |
加载完整的 Spring 应用程序上下文,用于集成测试。 | 所有类型的集成测试 |
webEnvironment = WebEnvironment.MOCK |
默认值。提供模拟的 Servlet 环境,不启动真实服务器。依赖 @AutoConfigureMockMvc。 |
使用 MockMvc 进行控制层测试,速度较快 |
webEnvironment = WebEnvironment.RANDOM_PORT |
启动嵌入式服务器并监听随机端口。 | 使用 TestRestTemplate 或 WebTestClient 进行测试 |
webEnvironment = WebEnvironment.DEFINED_PORT |
使用 application.properties 中定义的端口(或默认的 8080)启动服务器。 |
需要固定端口的测试场景 |
@AutoConfigureMockMvc |
自动配置 MockMvc 实例,用于模拟 MVC 请求。通常与 WebEnvironment.MOCK 结合使用。 |
使用 MockMvc 进行测试时必需 |
@Test |
JUnit Jupiter 注解,标记一个方法为测试方法。 | 所有测试方法 |
@Import |
显式导入特定的配置类,用于测试。 | 需要覆盖特定配置或引入测试专用配置时 |
在集成测试中,你通常希望测试完整的集成链,因此应尽量避免模拟(Mocking)。但如果某些外部依赖(如数据库、第三方服务)无法在测试环境中使用,或者你想隔离测试特定层,Spring Boot 提供了 @MockBean 注解来模拟 Bean。
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.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import com.example.service.UserService;
import com.fasterxml.jackson.databind.ObjectMapper;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
@AutoConfigureMockMvc
public class ExampleControllerWithMockServiceTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@MockBean // 模拟 UserService 这个 Bean,真实的 UserService 不会被调用
private UserService userService;
@Test
public void testCreateUserWithMockService() throws Exception {
User inputUser = new User("Charlie", "charlie@example.com");
User savedUser = new User(1L, "Charlie", "charlie@example.com"); // 假设保存后有了 ID
// 模拟 userService.save() 方法的行为
when(userService.save(any(User.class))).thenReturn(savedUser);
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(inputUser)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.name").value("Charlie"))
.andExpect(jsonPath("$.email").value("charlie@example.com"));
}
}
你可以使用 IDE 中的运行测试功能,或者通过 Maven 命令运行测试:
mvn test
Maven 会在 src/test/java 目录下查找测试类并运行。
@SpringBootTest 加载完整的应用程序上下文,测试各个组件之间的集成情况。perform 发起请求,andExpect 进行断言。WebEnvironment.RANDOM_PORT 可以避免测试时的端口冲突问题。希望这个指南能帮助你在 Spring Boot 3.x 中顺利编写控制器的集成测试!
作者:仓储大叔,张占岭,
荣誉:微软MVP
QQ:853066980
支付宝扫一扫,为大叔打赏!
