单元测试实战:测试 RESTful 接口
在 Spring Boot 项目中,对 RESTful 接口进行单元测试是保障后端质量的关键环节。相比传统的 Mock 方式,直接使用集成测试可以更真实地模拟用户请求。
基础配置与依赖注入
测试类通常放在与被测 Controller 相同的包路径下,方便访问默认权限的成员。我们需要给测试类加上 @SpringBootTest 注解,并将 webEnvironment 设置为 RANDOM_PORT。这样做的好处是每次运行测试时,Spring 容器会启动一个全新的应用实例,且端口随机分配,避免了多任务并发时的端口冲突问题。
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 static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class BookControllerTest {
// 容器会自动注入 TestRestTemplate,用于发送 HTTP 请求
@Autowired
private TestRestTemplate testRestTemplate;
// ... 后续代码
}
单次请求测试
拿到 TestRestTemplate 实例后,就可以像写业务代码一样调用接口了。以新增图书为例,我们构造一个 Book 对象,通过 postForObject 方法发送 POST 请求,并指定返回类型为 Book.class。
@Test
public void testAddBookRestful() {
// 准备测试数据
Book book = new Book("mysql", new BigDecimal(100), "ljh");
// 发起 POST 请求,/books 是接口路径
Book b = testRestTemplate.postForObject("/books", book, Book.class);
// 断言返回值是否符合预期
assertEquals("mysql", b.getName());
assertEquals(BigDecimal.valueOf(100), b.getPrice());
assertEquals("ljh", b.getAuthor());
}
注意: 这里有一个常见的坑。如果被测试的 Controller 方法没有加 @RequestBody 注解,Spring 就无法将 JSON 数据绑定到参数上,导致参数为 null。所以开发时务必确认接口方法的入参注解是否完整。
参数化测试优化
如果我们要测试多种不同的输入情况,一个个写测试方法会很累。JUnit 5 提供了 @ParameterizedTest 配合 @CsvSource 来简化这个过程。这允许我们用一行注解定义多组测试数据,自动循环执行。
@ParameterizedTest
@CsvSource({"书籍 A,100,ljh", "书籍 B,110,ljh", "书籍 C,120,ljh"})
public void testAddBookRestful(String name, BigDecimal price, String author) {
Book book = new Book(name, price, author);
Book b = testRestTemplate.postForObject("/books", book, Book.class);
assertEquals(name, b.getName());
assertEquals(price, b.getPrice());
assertEquals(author, b.getAuthor());
}
这种方式不仅减少了代码量,还能让测试报告更清晰地展示每一组数据的执行情况。对于查询或删除操作,思路也是一样的,只需更换 getForObject 或 deleteForEntity 等方法即可。
通过这种集成测试方式,我们可以快速验证接口的连通性和数据流转的正确性,是构建健壮后端服务的基础。

