You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Java编写的Pact契约请求体中注入动态ID?

问题:Pact Provider动态注入作业ID到请求体未生效

我们有一个用于根据ID更新作业的PUT API。测试后需要清理数据,原作业ID被删除后新生成的作业ID会变化,因此尝试从Provider端将动态ID注入请求体,但当前配置未生效,请求仍使用示例ID发起。


Provider类代码

@Slf4j
@Provider("Assignments API")
@Consumer("LTI-AGS-Tool")
//@PactBroker(url = BROKER_PACT_URL, authentication = @PactBrokerAuth(token = "${pactbroker.auth.token}"))
@VerificationReports(value = {"console", "markdown"}, reportDir = "target/pacts")
class PactProviderLTIAGSIT {

    private HashMap<String, String> headers = new HashMap<>();
    private String updateAssignmentId;
    private final String SERVICE_TOKEN = "myToken";

    @BeforeEach
    void createTeacherAssignment() {

        String assignmentBody = createBodyStringForStudentAssignmentSetup();

        assignmentBody = assignmentBody.replace("CPWAG", "OTHER_TEXT_RESOURCE");

        headers.put("Content-Type", "application/json");
        headers.put("Authorization", "myToken");

        RequestSpecification rq = Util.getRequestSpecification().baseUri(baseAssignmentUrl).headers(headers);
        Response response = rq.body(assignmentBody).post();
        assertEquals(201, response.getStatusCode());

        updateAssignmentId = response.jsonPath().get("assignments[0].refId");

        log.info("assignment id is " + updateAssignmentId);
    }

    @TestTemplate
    @ExtendWith(PactVerificationInvocationContextProvider.class)
    void pactTestTemplate(PactVerificationContext context, HttpRequest request) {

        request.addHeader("Authorization", SERVICE_TOKEN);
        logCurlFromPact(context, request);
        context.verifyInteraction();
    }

    @BeforeEach
    void before(PactVerificationContext context) {
        context.setTarget(new HttpsTestTarget(BASE_PACT_TEACHER_ASSIGNMENTS_URL, 443, ""));
    }

    @State("Scoring info is passed between ags-tool and assignmentapi")
    Map<String, Object> getScoringInfo() {

        Map<String, Object> map = new HashMap<>();
        map.put("assignmentId", updateAssignmentId);
        return map;
    }
}

Consumer契约代码

@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");

        //Body given and returned
        DslPart body = new PactDslJsonBody()
                .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
                .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)
                .toPact();

    }

    @Test
    @PactTestFor(pactMethod = "scoreConsumerPact", providerName = PACT_PROVIDER, port = "8080", pactVersion = PactSpecVersion.V3)
    void runTest(MockServer mockServer) {

        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);

        //Mock url
        RequestSpecification rq = Util.getRequestSpecification().baseUri(mockServer.getUrl()).headers(headers);

        Response response = rq.body(map)
                .post(path);

        assertEquals(201, response.getStatusCode());
    }
}

修复方案

1. 修复Provider中@BeforeEach执行顺序问题

JUnit的@BeforeEach执行顺序不固定,当前两个@BeforeEach可能导致updateAssignmentId未赋值就进入@State方法。合并两个方法,确保先创建作业拿到ID,再设置测试目标:

@BeforeEach
void setup(PactVerificationContext context) {
    // 先创建作业获取动态ID
    String assignmentBody = createBodyStringForStudentAssignmentSetup();
    assignmentBody = assignmentBody.replace("CPWAG", "OTHER_TEXT_RESOURCE");

    headers.put("Content-Type", "application/json");
    headers.put("Authorization", "myToken");

    RequestSpecification rq = Util.getRequestSpecification().baseUri(baseAssignmentUrl).headers(headers);
    Response response = rq.body(assignmentBody).post();
    assertEquals(201, response.getStatusCode());

    updateAssignmentId = response.jsonPath().get("assignments[0].refId");
    log.info("assignment id is " + updateAssignmentId);

    // 再设置测试目标
    context.setTarget(new HttpsTestTarget(BASE_PACT_TEACHER_ASSIGNMENTS_URL, 443, ""));
}

2. 修正请求方法不一致问题

问题描述中提到是PUT API,但Consumer契约和测试代码用的是POST,统一修改为PUT:

  • Consumer契约中:.method("POST") → .method("PUT")
  • Consumer测试代码中:.post(path) → .put(path)

3. 手动强制替换请求体中的ID(兜底方案)

如果上述步骤仍不生效,在Provider的测试模板中手动替换请求体里的示例ID:

@TestTemplate
@ExtendWith(PactVerificationInvocationContextProvider.class)
void pactTestTemplate(PactVerificationContext context, HttpRequest request) {
    request.addHeader("Authorization", SERVICE_TOKEN);
    
    // 替换请求体中的示例ID为动态生成的ID
    if (request.getBody() != null) {
        String bodyStr = new String(request.getBody(), StandardCharsets.UTF_8);
        bodyStr = bodyStr.replace("c1ef3bbf-55a2-4638-8f93-22b2916fe085", updateAssignmentId);
        request.setBody(bodyStr.getBytes(StandardCharsets.UTF_8));
    }
    
    logCurlFromPact(context, request);
    context.verifyInteraction();
}

4. 验证@State方法的返回值

在getScoringInfo方法中添加日志,确认updateAssignmentId不为空:

@State("Scoring info is passed between ags-tool and assignmentapi")
Map<String, Object> getScoringInfo() {
    log.info("Providing assignmentId from state: {}", updateAssignmentId);
    Map<String, Object> map = new HashMap<>();
    map.put("assignmentId", updateAssignmentId);
    return map;
}

内容的提问来源于stack exchange,提问作者Francislainy Campos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 13:05:20