springboot~3.x项目中使用集成测试
<p>在 Spring Boot 3.x 中为控制器编写集成测试,主要是通过 <code>@SpringBootTest</code> 注解加载完整的应用上下文,并利用 <code>MockMvc</code> 或 <code>TestRestTemplate</code> 来模拟 HTTP 请求并验证响应。下面我将为你提供一个清晰的指南和代码示例。</p><h1 id="两种测试">两种测试</h1>
<p>在Spring Boot项目中,测试通常分为单元测试和集成测试。以下是区分这两种测试的一些指导原则:</p>
<h3 id="单元测试">单元测试</h3>
<ul>
<li><strong>定义</strong>:单元测试主要用于测试代码中的单个“单元”,通常是一个方法或类。它们的目标是验证特定功能的正确性。</li>
<li><strong>特征</strong>:
<ul>
<li><strong>独立性</strong>:单元测试不依赖于外部系统(如数据库、网络等)。</li>
<li><strong>快速执行</strong>:通常执行时间很短。</li>
<li><strong>使用Mock</strong>:通常会使用Mock对象来替代依赖项,以便只测试目标单元。</li>
</ul>
</li>
<li><strong>示例</strong>:<pre><code class="language-java">@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());
}
}
</code></pre>
</li>
</ul>
<h3 id="集成测试">集成测试</h3>
<ul>
<li><strong>定义</strong>:集成测试用于测试多个组件之间的交互,通常是测试整个应用程序或其部分的行为。</li>
<li><strong>特征</strong>:
<ul>
<li><strong>依赖性</strong>:集成测试通常会启动Spring上下文,并可能连接到数据库或其他外部服务。</li>
<li><strong>较慢执行</strong>:由于涉及多个组件,执行时间通常较长。</li>
<li><strong>真实环境</strong>:测试在接近真实环境的条件下运行。</li>
</ul>
</li>
<li><strong>示例</strong>:<pre><code class="language-java">@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"));
}
}
</code></pre>
</li>
</ul>
<h3 id="如何区分">如何区分</h3>
<ol>
<li>
<p><strong>测试目标</strong>:</p>
<ul>
<li><strong>单元测试</strong>:关注单个类或方法。</li>
<li><strong>集成测试</strong>:关注多个组件的协作。</li>
</ul>
</li>
<li>
<p><strong>使用的工具</strong>:</p>
<ul>
<li><strong>单元测试</strong>:Mockito、JUnit等。</li>
<li><strong>集成测试</strong>:Spring Test、MockMvc等。</li>
</ul>
</li>
<li>
<p><strong>项目结构</strong>:</p>
<ul>
<li>可以在<code>src/test/java</code>目录中创建不同的包,例如<code>unit</code>和<code>integration</code>,分别存放单元测试和集成测试。</li>
</ul>
</li>
<li>
<p><strong>命名约定</strong>:</p>
<ul>
<li>可以在文件名中添加前缀或后缀,例如<code>UserServiceTest</code>(单元测试)和<code>UserControllerIntegrationTest</code>(集成测试)。</li>
</ul>
</li>
</ol>
<h1 id="-spring-boot-3x-控制器集成测试指南">🧪 Spring Boot 3.x 控制器集成测试指南</h1>
<h2 id="-1-添加测试依赖">📝 1. 添加测试依赖</h2>
<p>确保你的 <code>pom.xml</code> 中包含 Spring Boot Teststarter 依赖,它通常已经包含了 JUnit Jupiter、Mockito、AssertJ 等测试所需的库。</p>
<pre><code class="language-xml"><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</code></pre>
<h2 id="-2-创建控制器">🧱 2. 创建控制器</h2>
<p>假设你有一个简单的 REST 控制器 <code>ExampleController</code>:</p>
<pre><code class="language-java">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 需要通过依赖注入
}
}
</code></pre>
<h2 id="-3-编写集成测试类">🧪 3. 编写集成测试类</h2>
<p>创建一个集成测试类,使用 <code>@SpringBootTest</code> 和 <code>@AutoConfigureMockMvc</code> 来配置测试环境。</p>
<h3 id="31-使用-mockmvc-模拟-mvc-环境">3.1 使用 MockMvc (模拟 MVC 环境)</h3>
<p>这种方法不会启动真正的服务器,而是模拟 Servlet 环境,测试速度较快。</p>
<pre><code class="language-java">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"));
}
}
</code></pre>
<h3 id="32-使用-testresttemplate-启动真实服务器">3.2 使用 TestRestTemplate (启动真实服务器)</h3>
<p>这种方法会启动一个嵌入式的真实服务器(如 Tomcat),测试更接近于生产环境。</p>
<pre><code class="language-java">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");
}
}
</code></pre>
<h2 id="️-4-关键注解和配置说明">⚙️ 4. 关键注解和配置说明</h2>
<p>下表总结了集成测试中常用的注解及其用途:</p>
<table>
<thead>
<tr>
<th style="text-align: left">注解</th>
<th style="text-align: left">说明</th>
<th style="text-align: left">适用场景</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left"><code>@SpringBootTest</code></td>
<td style="text-align: left">加载完整的 Spring 应用程序上下文,用于集成测试。</td>
<td style="text-align: left">所有类型的集成测试</td>
</tr>
<tr>
<td style="text-align: left"><code>webEnvironment = WebEnvironment.MOCK</code></td>
<td style="text-align: left">默认值。提供模拟的 Servlet 环境,<strong>不启动真实服务器</strong>。依赖 <code>@AutoConfigureMockMvc</code>。</td>
<td style="text-align: left">使用 <code>MockMvc</code> 进行控制层测试,速度较快</td>
</tr>
<tr>
<td style="text-align: left"><code>webEnvironment = WebEnvironment.RANDOM_PORT</code></td>
<td style="text-align: left">启动嵌入式服务器并监听<strong>随机端口</strong>。</td>
<td style="text-align: left">使用 <code>TestRestTemplate</code> 或 <code>WebTestClient</code> 进行测试</td>
</tr>
<tr>
<td style="text-align: left"><code>webEnvironment = WebEnvironment.DEFINED_PORT</code></td>
<td style="text-align: left">使用 <code>application.properties</code> 中定义的端口(或默认的 8080)启动服务器。</td>
<td style="text-align: left">需要固定端口的测试场景</td>
</tr>
<tr>
<td style="text-align: left"><code>@AutoConfigureMockMvc</code></td>
<td style="text-align: left">自动配置 <code>MockMvc</code> 实例,用于模拟 MVC 请求。通常与 <code>WebEnvironment.MOCK</code> 结合使用。</td>
<td style="text-align: left">使用 <code>MockMvc</code> 进行测试时必需</td>
</tr>
<tr>
<td style="text-align: left"><code>@Test</code></td>
<td style="text-align: left">JUnit Jupiter 注解,标记一个方法为测试方法。</td>
<td style="text-align: left">所有测试方法</td>
</tr>
<tr>
<td style="text-align: left"><code>@Import</code></td>
<td style="text-align: left">显式导入特定的配置类,用于测试。</td>
<td style="text-align: left">需要覆盖特定配置或引入测试专用配置时</td>
</tr>
</tbody>
</table>
<h2 id="-5-处理依赖和模拟mocking">🧰 5. 处理依赖和模拟(Mocking)</h2>
<p>在集成测试中,你通常希望测试完整的集成链,因此应尽量避免模拟(Mocking)。但如果某些外部依赖(如数据库、第三方服务)无法在测试环境中使用,或者你想隔离测试特定层,Spring Boot 提供了 <code>@MockBean</code> 注解来模拟 Bean。</p>
<pre><code class="language-java">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"));
}
}
</code></pre>
<h2 id="-6-运行测试">🚀 6. 运行测试</h2>
<p>你可以使用 IDE 中的运行测试功能,或者通过 Maven 命令运行测试:</p>
<pre><code class="language-bash">mvn test
</code></pre>
<p>Maven 会在 <code>src/test/java</code> 目录下查找测试类并运行。</p>
<h2 id="-核心要点">💎 核心要点</h2>
<ul>
<li><strong>集成测试 (Integration Test)</strong>:使用 <code>@SpringBootTest</code> 加载完整的应用程序上下文,测试各个组件之间的集成情况。</li>
<li><strong>MockMvc</strong>:适用于<strong>模拟 MVC 环境</strong>的测试,无需启动真实服务器,速度快。 使用 <code>perform</code> 发起请求,<code>andExpect</code> 进行断言。</li>
<li><strong>TestRestTemplate</strong>:适用于<strong>启动真实嵌入式服务器</strong>的测试,更接近真实环境。 直接发起 HTTP 请求并接收响应。</li>
<li><strong>随机端口</strong>:使用 <code>WebEnvironment.RANDOM_PORT</code> 可以避免测试时的端口冲突问题。</li>
<li><strong>@MockBean</strong>:当你需要模拟应用程序上下文中的某个 Bean 时(例如模拟一个不易在测试环境中构建的外部服务),可以使用此注解。</li>
</ul>
<p>希望这个指南能帮助你在 Spring Boot 3.x 中顺利编写控制器的集成测试!</p>
</div>
<div id="MySignature" role="contentinfo">
<p></p>
<div class="navgood">
<p>作者:仓储大叔,张占岭,<br>
荣誉:微软MVP<br>QQ:853066980</p>
<p><strong>支付宝扫一扫,为大叔打赏!</strong>
<br><img src="https://images.cnblogs.com/cnblogs_com/lori/237884/o_IMG_7144.JPG"></p>
</div><br><br>
来源:https://www.cnblogs.com/lori/p/19059332
頁:
[1]