API 测试这几年越来越常见。它不依赖 UI,执行速度通常更快,也更容易接入自动化流程,因此很适合用来验证系统的端到端能力。把 API 自动化测试接入 CI/CD 流水线后,团队也能更早拿到构建反馈。
这篇文章聚焦一个具体场景:如何用 REST-Assured 和 Java 测试 DELETE API 请求。DELETE 不是一个只看状态码就能放心的接口类型,它会直接删除服务端资源,所以测试时既要验证删除请求本身,也要验证删除后的资源状态。
DELETE API 请求用于从服务端删除指定资源。大多数情况下,DELETE 请求不会返回响应体,接口通常会通过状态码告诉调用方删除是否成功。
被删除的资源一般由 URI 或路径参数定位。请求处理完成后,该资源会从系统中移除。也正因为如此,DELETE 操作可能产生明显副作用,比如删除数据库中的订单记录。
测试 DELETE 接口时,有几个点需要特别注意:
测试 DELETE 接口时,真正要确认的不是请求有没有发出去,而是目标资源是否按预期被删除,并且系统对重复删除、无效资源和无权限请求都有明确响应。
本文使用一个免费的 RESTful 电商 API 项目。这个项目提供了和订单管理相关的多个接口,可以执行创建订单、查询订单、更新订单和删除订单等操作。DELETE 接口如下:
DELETE /deleteOrder/{id}
这个接口要求调用方通过路径参数传入 order_id,用于定位需要删除的订单。DELETE 请求不需要传请求体,但出于安全考虑,请求头中必须携带有效的认证 token。
接口执行成功后,系统会删除指定订单,并返回 204 No Content。如果订单不存在,或者 token 缺失、无效,接口会返回对应的错误响应。
DELETE 接口负责删除系统数据,所以测试重点不能只停留在请求成功。更稳妥的做法是先构造可删除的数据,再执行删除请求,最后再查询一次资源状态。设计两个测试场景:
这样做的好处是测试闭环更完整:第一个断言证明删除请求被服务端接受,第二个断言证明删除结果确实落到了系统状态上。
先创建一个 Java 测试类 TestDeleteRequestExamples,并添加 testTokenGeneration() 方法,用来获取认证 token。
public class TestDeleteRequestExamples {
// 保存认证接口返回的 token,后续删除订单时复用
private String token;
@Test
public void testTokenGeneration() {
// 示例账号密码仅用于演示,真实项目不要硬编码在测试代码里
String requestBody = """
{
"username": "FunTester-admin",
"password": "FunTester-secretPass123"
}""";
token = given()
.contentType(ContentType.JSON) // 声明请求体为 JSON 格式
.when()
.body(requestBody) // 传入登录请求体
.post("http://localhost:3004/auth") // 调用认证接口获取 token
.then()
.statusCode(201) // 认证成功后应返回 201
.and()
.body("token", notNullValue()) // 校验响应中必须包含 token 字段
.extract()
.path("token"); // 后续 DELETE 请求复用这个认证 token
}
}
testTokenGeneration() 会向 /auth 发送一个 POST 请求,请求体中包含用户名和密码。示例项目为了演示方便,直接把登录信息写在字符串中;在真实项目里,账号、密码、token 这类敏感信息不应该硬编码,建议通过环境变量、配置中心或密钥管理服务读取。
这个测试会验证响应状态码为 201,并检查响应体中存在 token 字段。随后,它会把 token 提取出来,保存到类成员变量中,供后续 DELETE 请求使用。
拿到 token 以后,就可以实现删除订单的测试方法。
public class TestDeleteRequestExamples {
// 删除接口需要认证信息
private String token;
// 示例订单 ID,真实项目建议动态创建测试订单
int orderId = 1;
@Test
public void testDeleteOrder() {
given()
.header("Authorization", token) // DELETE 操作需要认证信息
.log()
.all() // 打印请求日志,方便排查失败原因
.when()
.delete("http://localhost:3004/deleteOrder/" + orderId) // 删除指定订单
.then()
.log()
.all() // 打印响应日志,便于确认服务端返回
.statusCode(204); // 删除成功后预期返回 204
}
}
这里有几个关键点:
header() 用于添加 Authorization 请求头,确保接口调用具备删除权限。token 来自前一个认证接口,用来证明当前请求是经过授权的。delete() 会向 /deleteOrder/{id} 发送 DELETE 请求,要求服务端删除指定订单。statusCode() 验证响应状态码是否为 204,也就是删除成功且没有响应体。第二个场景用于验证删除后的系统状态。测试步骤很简单:
GET /getOrder 查询刚刚删除的订单。order_id 作为查询参数。测试代码如下:
@Test
public void testFetchingDeletedOrder() {
given()
.when()
.queryParam("id", orderId) // 查询刚刚被删除的订单
.get("http://localhost:3004/getOrder") // 通过查询接口验证资源是否还存在
.then()
.statusCode(404); // 删除后再次查询应返回 404
}
这个方法会用刚才删除的 orderId 再次查询订单。如果接口返回 404 Not Found,说明该资源已经不可查询,删除结果得到了进一步验证。严格来说,前一个 DELETE 测试中的 204 断言已经说明接口接受了删除请求。但在端到端测试里,删除后再查询一次更稳妥,尤其适合验证数据库状态、缓存失效、读写链路一致性这类问题。
DELETE 接口还有一个容易被忽略的点:删除操作通常不是孤立动作。订单删除可能牵涉订单表、订单明细、库存、支付记录、优惠券、通知、审计日志,甚至缓存和搜索索引。如果系统是微服务架构,删除请求返回成功,并不一定代表所有下游状态都已经同步完成。
并发场景下,至少要考虑两个问题:
并发删除可以用下面这种思路验证:
@Test
public void testDeletingSameOrderConcurrently() {
// 第一个并发删除请求
CompletableFuture<Integer> firstDelete = CompletableFuture.supplyAsync(() ->
deleteOrderAndReturnStatus(orderId)
);
// 第二个并发删除请求,和第一个请求竞争同一条订单
CompletableFuture<Integer> secondDelete = CompletableFuture.supplyAsync(() ->
deleteOrderAndReturnStatus(orderId)
);
// 等待两个删除请求都完成,并收集它们的状态码
List<Integer> statusCodes = Stream.of(firstDelete, secondDelete)
.map(CompletableFuture::join)
.toList();
// 至少有一个请求真正删除成功
assertThat(statusCodes, hasItem(204));
// 另一个请求应返回稳定、可解释的结果,例如资源不存在或冲突
assertThat(statusCodes, anyOf(
hasItem(404),
hasItem(409)
));
}
private int deleteOrderAndReturnStatus(int orderId) {
return given()
.header("Authorization", token) // 并发删除同样需要认证信息
.when()
.delete("/deleteOrder/" + orderId) // 删除同一条订单
.then()
.extract()
.statusCode(); // 返回状态码,交给并发用例统一判断
}
这里不要机械要求两个请求都返回 204。更合理的契约通常是:只有一个请求真正删除成功,另一个请求返回 404 或 409。到底应该是哪一个,要看接口契约怎么定义。测试要验证的是系统行为稳定、可解释,而不是强行把所有状态码都改成成功。
删除后的查询也要分同步和异步两种情况。如果接口返回 204,一般表示删除已经完成,马上查询应该返回 404。如果接口返回 202 Accepted,通常说明服务端只是接收了删除任务,后台还在处理,这时测试就不能立刻断言资源不存在,而应该轮询查询,直到资源消失或超时。
删除接口越靠近真实业务,越不能只测请求响应,还要测并发边界和删除后系统状态是否收敛到一致结果。