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
核心问题定位
- 请求体赋值时机错误:契约定义阶段
requestBody尚未赋值,导致Pact预期空请求体,但实际请求体是在VerifyAsync方法内部生成的,两者完全不匹配。 - 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
相关产品推荐
相关产品推荐

