
本文详细介绍了在Java中使用Pact进行契约测试时,如何动态注入请求体中的ID。通过结合Pact Provider的@State注解和Consumer端PactDslJsonBody的valueFromProviderState方法,实现了在测试前创建数据并将其动态ID传递给Consumer契约,确保契约测试的灵活性和真实性,避免硬编码ID带来的问题。
在微服务架构中,契约测试是确保服务间兼容性的关键环节。Pact作为一种流行的契约测试框架,允许消费者和生产者独立验证它们之间的API契约。然而,当API请求体中的某些字段(如ID)是动态生成时,如何有效地在Pact契约中处理这些动态值成为了一个挑战。本文将深入探讨在Java Pact契约中动态注入请求体ID的方法。
在实际应用中,许多API操作(例如更新资源)需要一个预先存在的ID。在测试环境中,为了保证测试的隔离性和数据清洁,我们通常会在每次测试前创建新的数据,这意味着其ID是动态的。如果Pact契约中硬编码了这些ID,那么在Provider端执行契约验证时,这些硬编码的ID可能不再有效,导致测试失败。
Pact通过Provider States(生产者状态)机制解决了这一问题。Provider States允许生产者在运行契约验证之前,根据消费者定义的状态设置其系统,包括创建必要的测试数据并提供动态生成的数据(如ID)给消费者契约。
立即学习“Java免费学习笔记(深入)”;
生产者需要定义一个或多个状态,这些状态会在契约验证之前执行,用于准备测试数据。
在Provider类中,我们使用@BeforeEach注解来设置测试前的数据,并使用@State注解来定义一个Provider State。
@Slf4j
@Provider("Assignments API")
@Consumer("LTI-AGS-Tool")
@VerificationReports(value = {"console", "markdown"}, reportDir = "target/pacts")
class PactProviderLTIAGSIT {
private HashMap<String, String> headers = new HashMap<>();
private String updateAssignmentId; // 用于存储动态生成的ID
private final String SERVICE_TOKEN = "myToken";
@BeforeEach
void createTeacherAssignment() {
// ... (省略创建assignmentBody的逻辑) ...
String assignmentBody = createBodyStringForStudentAssignmentSetup(); // 假设此方法生成初始body
assignmentBody = assignmentBody.replace("CPWAG", "OTHER_TEXT_RESOURCE");
headers.put("Content-Type", "application/json");
headers.put("Authorization", SERVICE_TOKEN); // 使用SERVICE_TOKEN
// 假设 Util.getRequestSpecification() 提供了一个可配置的 RequestSpecification
RequestSpecification rq = Util.getRequestSpecification().baseUri(baseAssignmentUrl).headers(headers);
Response response = rq.body(assignmentBody).post();
assertEquals(201, response.getStatusCode());
// 从响应中提取动态生成的ID
updateAssignmentId = response.jsonPath().get("assignments[0].refId");
log.info("assignment id is " + updateAssignmentId);
}
// 定义一个Provider State,用于将动态ID传递给消费者
@State("Scoring info is passed between ags-tool and assignmentapi")
Map<String, Object> getScoringInfo() {
Map<String, Object> map = new HashMap<>();
// 将动态生成的ID放入map中,Pact将通过此map传递给消费者契约
map.put("assignmentId", updateAssignmentId);
return map;
}
@BeforeEach
void before(PactVerificationContext context) {
// 设置Pact验证的目标服务
context.setTarget(new HttpsTestTarget(BASE_PACT_TEACHER_ASSIGNMENTS_URL, 443, ""));
}
@TestTemplate
@ExtendWith(PactVerificationInvocationContextProvider.class)
void pactTestTemplate(PactVerificationContext context, HttpRequest request) {
request.addHeader("Authorization", SERVICE_TOKEN);
// logCurlFromPact(context, request); // 可选:打印CURL请求
context.verifyInteraction();
}
}关键点说明:
消费者契约需要声明它依赖于特定的Provider State,并且在请求体中使用Pact提供的DSL来引用Provider State中传递的动态值。
在消费者契约中,我们使用PactDslJsonBody的valueFromProviderState方法来动态地填充请求体中的ID。
@ExtendWith(PactConsumerTestExt.class)
class PactConsumerSendScoreIT {
private final Map<String, String> headers = new HashMap<>();
private final String path = "/v5/assignmentStatus/update";
@Pact(provider = PACT_PROVIDER, consumer = PACT_CONSUMER)
public RequestResponsePact scoreConsumerPact(PactDslWithProvider builder) {
headers.put("Content-Type", "application/json");
DslPart body = new PactDslJsonBody()
// 关键:使用 valueFromProviderState 动态注入 ID
// 第一个参数: 契约中此字段的名称
// 第二个参数: 引用Provider State中map的键,对于字符串类型需要使用 ${} 进行插值
// 第三个参数: 消费者端测试时使用的示例值(当没有真实Provider时)
.valueFromProviderState("assignmentId", "${assignmentId}", "c1ef3bbf-55a2-4638-8f93-22b2916fe085")
.stringType("timestamp", DateTime.now().plusHours(3).toString())
.decimalType("scoreGiven", 75.00)
.decimalType("scoreMaximum", 100.00)
.stringType("comment", "Good work!")
.stringType("status", "IN_PROGRESS")
.stringType("userId", "c2ef3bbf-55a2-4638-8f93-22b2916fe085")
.close();
return builder
// 声明此契约依赖于名为 "Scoring info is passed between ags-tool and assignmentapi" 的Provider State
.given("Scoring info is passed between ags-tool and assignmentapi")
.uponReceiving("Scoring info is passed between ags-tool and assignmentapi")
.path(path)
.method("POST")
.body(body)
.headers(headers)
.willRespondWith()
.status(201)
.body(body) // 响应体中也可能包含动态ID,此处示例与请求体相同
.toPact();
}
@Test
@PactTestFor(pactMethod = "scoreConsumerPact", providerName = PACT_PROVIDER, port = "8080", pactVersion = PactSpecVersion.V3)
void runTest(MockServer mockServer) {
// 消费者测试时,使用示例值或者自行模拟一个ID
String updateAssignmentId = "c2ef3bbf-55a2-4638-8f93-22b2916fe085";
HashMap<String, Object> map = new HashMap<>();
map.put("timestamp", DateTime.now().plusHours(3).toString());
map.put("scoreGiven", 75.00);
map.put("scoreMaximum", 100.00);
map.put("comment", "Good work!");
map.put("status", "IN_PROGRESS");
map.put("userId", "c2ef3bbf-55a2-4638-8f93-22b2916fe085");
map.put("assignmentId", updateAssignmentId); // 消费者测试时使用此ID
RequestSpecification rq = Util.getRequestSpecification().baseUri(mockServer.getUrl()).headers(headers);
Response response = rq.body(map)
.post(path);
assertEquals(201, response.getStatusCode());
}
}关键点说明:
通过上述方法,我们成功地在Java Pact契约中实现了动态ID的注入。这种机制使得契约测试能够更好地适应真实世界中数据动态变化的场景,提高了测试的鲁棒性和实用性,是编写高质量契约测试的关键实践。
以上就是如何在Java Pact契约中动态注入Body ID的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号