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

Pact Mock Server请求不匹配(Request-Mismatch)排查求助

问题排查:Pact契约PUT请求的Request-Mismatch错误

问题描述

定义Pact契约后,向Mock Server发起指定PUT请求时触发Request-Mismatch错误。已确认发送的消息字符串匹配,调整请求头后仍未解决,需排查代码问题并定位原因。

相关代码

初始化Pact配置

var config = new PactConfig
{
    PactDir = PactDir,
    Outputters = new List<IOutput> { new XunitOutput(output), new ConsoleOutput() },
    LogLevel = PactLogLevel.Trace
};

_pact = PactNet.Pact.V3(_consumerName, _providerName, config).WithHttpInteractions();

契约定义与验证逻辑

// Define overall contract and pact mock behavior
_pact
    // Define request made by consumer to provider
    .UponReceiving("Object has null callback URL")
        .Given("my provider state here")
        .WithRequest(HttpMethod.Put, "/do_thing")
        .WithHeader("Accept", "application/json; charset=utf-8")
        .WithHeader("content-length", "170")
        .WithHeader("Authorization", "Bearer")
        .WithHeader("Host", mockUrl)
        .WithBody(requestBody, "application/json; charset=utf-8")
    // Define response by provider to consumer 
    .WillRespond()
        .WithStatus(HttpStatusCode.OK)
        .WithHeader("Content-Type", "application/json; charset=utf-8")
        .WithJsonBody(expectedJobResponseDyn);

await _pact
    .VerifyAsync(async context =>
    {
        mockUrl = context.MockServerUri.ToString().TrimEnd('/');
        MyObject myObject = new() { CallbackUri = mockUrl};
        requestBody = JsonSerializer.Serialize(myObject);

        // Act //////////
        ResponseObject result = await client.SendRequestAsync(myObject);

        // Assert //////////
        result.Code.Should().Be(someOtherObj.Code);
        result.Message.Should().Be(someOtherObj.Message);
    });

错误响应信息

{"error":"Request-Mismatch : HttpRequest { method: \"PUT\", path: \"/do_thing\", query: None, headers: Some({\"host\": [\"127.0.0.1:58615\"], \"authorization\": [\"Bearer\"], \"content-length\": [\"170\"], \"content-type\": [\"application/json; charset=utf-8\"]}), body: Present(b\"{\\\"JobId\\\":\\\"00000000-0000-0000-0000-000000000000\\\",\\\"CallbackUri\\\":\\\"http://127.0.0.1:58615\\\"}\", Some(ContentType { main_type: \"application\", sub_type: \"json\", attributes: {\"charset\": \"utf-8\"}, suffix: None }), None), matching_rules: MatchingRules { rules: {} }, generators: Generators { categories: {} } }"}

关键错误线索(来自Trace日志)

  • 预期请求体为空字符串'',实际发送的是包含JobId和CallbackUri的JSON对象
  • 预期Content-Type为application/json,实际为application/json; charset=utf-8

核心问题定位

  1. 请求体赋值时机错误:契约定义阶段requestBody尚未赋值,导致Pact预期空请求体,但实际请求体是在VerifyAsync方法内部生成的,两者完全不匹配。
  2. Content-Type定义不一致:契约中WithBody指定的Content-Type与实际请求发送的Content-Type存在差异(忽略了charset参数),触发头信息匹配失败。

修复方案

方案1:提前生成请求体,保证契约与实际请求一致

将requestBody的生成逻辑移到契约定义之前,确保契约预期的请求体和实际发送的内容一致:

// 先生成测试用请求体(若需动态Mock地址,可使用固定测试值或Pact生成器)
var testMockUrl = "http://test-mock-url";
MyObject myObject = new() { CallbackUri = testMockUrl};
requestBody = JsonSerializer.Serialize(myObject);

// 再定义契约
_pact
    .UponReceiving("Object has null callback URL")
        .Given("my provider state here")
        .WithRequest(HttpMethod.Put, "/do_thing")
        .WithHeader("Accept", "application/json; charset=utf-8")
        // 建议去掉硬编码的content-length,让Pact自动计算,避免内容变化导致匹配失败
        // .WithHeader("content-length", "170")
        .WithHeader("Authorization", "Bearer")
        .WithHeader("Host", testMockUrl)
        .WithBody(requestBody, "application/json; charset=utf-8")
    .WillRespond()
        .WithStatus(HttpStatusCode.OK)
        .WithHeader("Content-Type", "application/json; charset=utf-8")
        .WithJsonBody(expectedJobResponseDyn);

await _pact
    .VerifyAsync(async context =>
    {
        mockUrl = context.MockServerUri.ToString().TrimEnd('/');
        MyObject actualObject = new() { CallbackUri = mockUrl};

        // Act //////////
        ResponseObject result = await client.SendRequestAsync(actualObject);

        // Assert //////////
        result.Code.Should().Be(someOtherObj.Code);
        result.Message.Should().Be(someOtherObj.Message);
    });

方案2:使用Pact匹配规则,实现宽松匹配

如果请求体内容存在动态值(比如Mock Server的端口),可以使用Pact的匹配规则避免硬编码:

_pact
    .UponReceiving("Object has null callback URL")
        .Given("my provider state here")
        .WithRequest(HttpMethod.Put, "/do_thing")
        .WithHeader("Accept", "application/json; charset=utf-8")
        .WithHeader("Authorization", "Bearer")
        // 使用正则匹配忽略Host的具体端口
        .WithHeader("Host", Match.Regex(".*", "mock-server-host"))
        // 使用结构匹配代替固定内容,兼容动态值
        .WithJsonBody(Match.MinType(new {
            JobId = Match.Guid(),
            CallbackUri = Match.Regex("http://.*:[0-9]+", "http://mock-server:port")
        }), "application/json; charset=utf-8")
    .WillRespond()
        .WithStatus(HttpStatusCode.OK)
        .WithHeader("Content-Type", "application/json; charset=utf-8")
        .WithJsonBody(expectedJobResponseDyn);

额外优化建议

  • 去掉硬编码的content-length头:Pact会自动计算请求体长度,硬编码容易因内容变化导致匹配失败。
  • 统一Content-Type格式:契约和实际请求的Content-Type保持完全一致,或使用匹配规则忽略charset属性。

内容的提问来源于stack exchange,提问作者David M

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 10:17:03