Pact Net契约验证失败:GET接口返回404及响应头异常
Pact验证GET接口返回404问题
问题说明
测试GET接口时,预期返回200状态码,但实际返回404,且响应头不符合预期。
失败日志
Failures: 1) Verifying a pact between API Consumer and Weather API Given There is data - A GET request to retrieve the weather 1.1) has a matching body / -> Expected body Present(496 bytes) but was empty 1.2) has status code 200 expected 200 but was 404 1.3) includes header 'Content-Type' with value ""application/json; charset=utf-8"" Expected header 'Content-Type' to have value ""application/json; charset=utf-8"" but was '' There were 1 pact failures
验证器日志
Verifier Logs ------------- 2023-07-24T11:13:08.962868Z DEBUG ThreadId(01) verify_interaction{interaction="A GET request to retrieve the weather"}: pact_verifier: Executing provider states 2023-07-24T11:13:08.962901Z INFO ThreadId(01) verify_interaction{interaction="A GET request to retrieve the weather"}: pact_verifier: Running setup provider state change handler 'There is data' for 'A GET request to retrieve the weather forecasts' 2023-07-24T11:13:08.968810Z DEBUG ThreadId(01) verify_interaction{interaction="A GET request to retrieve the weather"}: pact_verifier::provider_client: Sending HTTP Request ( method: POST, path: /, query: None, headers: Some({"Content-Type": ["application/json"]}), body: Present(54 bytes, application/json) ) to state change handler 2023-07-24T11:13:08.969055Z DEBUG ThreadId(01) verify_interaction{interaction="A GET request to retrieve the weather"}: reqwest::connect: starting new connection: http://localhost:7058/ 2023-07-24T11:13:08.978147Z DEBUG ThreadId(01) verify_interaction{interaction="A GET request to retrieve the weather"}: hyper::client::connect::http: connecting to [::1]:7058 2023-07-24T11:13:08.978582Z DEBUG ThreadId(01) verify_interaction{interaction="A GET request to retrieve the weather"}: hyper::client::connect::http: connected to [::1]:7058 2023-07-24T11:13:09.107613Z DEBUG ThreadId(01) verify_interaction{interaction="A GET request to retrieve the weather"}: hyper::client::pool: pooling idle connection for ("http", localhost:7058) 2023-07-24T11:13:09.107649Z DEBUG ThreadId(01) verify_interaction{interaction="A GET request to retrieve the weather"}: pact_verifier::provider_client: State change request: Response { url: Url { scheme: "http", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("localhost")), port: Some(7058), path: "/provider-states", query: None, fragment: None }, status: 200, headers: {"content-length": "0", "date": "Mon, 24 Jul 2023 11:13:08 GMT", "server": "Kestrel"} } 2023-07-24T11:13:09.107736Z DEBUG ThreadId(01) verify_interaction{interaction="A GET request to retrieve the weather"}: pact_verifier: State Change: "ProviderState { name: "There is data", params: {} }" -> Ok({}) 2023-07-24T11:13:09.107750Z INFO ThreadId(01) verify_interaction{interaction="A GET request to retrieve the weather"}: pact_verifier: Running provider verification for 'A GET request to retrieve the weather' 2023-07-24T11:13:09.107789Z DEBUG ThreadId(01) verify_interaction{interaction="A GET request to retrieve the weather"}: pact_verifier: Verifying a HTTP interaction 2023-07-24T11:13:09.107817Z INFO ThreadId(01) verify_interaction{interaction="A GET request to retrieve the weather"}: pact_verifier::provider_client: Sending request to provider at http://localhost:7058/ 2023-07-24T11:13:09.107818Z DEBUG ThreadId(01) verify_interaction{interaction="A GET request to retrieve the weather"}: pact_verifier::provider_client: Provider details = ProviderInfo { name: "Weather API", protocol: "http", host: "localhost", port: Some(7058), path: "/", transports: [ProviderTransport { transport: "http", port: Some(7058), path: Some("/"), scheme: None }] } 2023-07-24T11:13:09.107829Z INFO ThreadId(01) verify_interaction{interaction="A GET request to retrieve the weather"}: pact_verifier::provider_client: Sending request HTTP Request ( method: GET, path: /weather-forecast, query: None, headers: None, body: Missing ) 2023-07-24T11:13:09.107831Z DEBUG ThreadId(01) verify_interaction{interaction="A GET request to retrieve the weather"}: pact_verifier::provider_client: body: 2023-07-24T11:13:09.107853Z DEBUG ThreadId(01) verify_interaction{interaction="A GET request to retrieve the weather"}: hyper::client::pool: reuse idle connection for ("http", localhost:7058) 2023-07-24T11:13:09.118997Z DEBUG ThreadId(01) verify_interaction{interaction="A GET request to retrieve the weather"}: hyper::client::pool: pooling idle connection for ("http", localhost:7058) 2023-07-24T11:13:09.119024Z DEBUG ThreadId(01) verify_interaction{interaction="A GET request to retrieve the weather"}: pact_verifier::provider_client: Received native response: Response { url: Url { scheme: "http", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("localhost")), port: Some(7058), path: "/weather-forecast", query: None, fragment: None }, status: 404, headers: {"content-length": "0", "date": "Mon, 24 Jul 2023 11:13:08 GMT", "server": "Kestrel"} } 2023-07-24T11:13:09.119062Z INFO ThreadId(01) verify_interaction{interaction="A GET request to retrieve the weather"}: pact_verifier::provider_client: Received response: HTTP Response ( status: 404, headers: Some({"content-length": ["0"], "date": ["Mon", "24 Jul 2023 11:13:08 GMT"], "server": ["Kestrel"]}), body: Empty ) 2023-07-24T11:13:09.119067Z DEBUG ThreadId(01) verify_interaction{interaction="A GET request to retrieve the weather"}: pact_verifier::provider_client: body: 2023-07-24T11:13:09.119088Z INFO ThreadId(01) verify_interaction{interaction="A GET request to retrieve the weather"}: pact_matching: comparing to expected response: HTTP Response ( status: 200, headers: Some({"Content-Type": ["application/json; charset=utf-8"]}), body: Present(496 bytes) ) 2023-07-24T11:13:09.119130Z DEBUG ThreadId(01) verify_interaction{interaction="A GET request to retrieve the weather forecasts"}: pact_matching: expected content type = 'application/json;charset=utf-8', actual content type = '*/*' 2023-07-24T11:13:09.119352Z DEBUG ThreadId(01) verify_interaction{interaction="A GET request to retrieve the weather forecasts"}: pact_matching: content type header matcher = 'RuleList { rules: [], rule_logic: And, cascaded: false }'
测试代码
public class ProviderTests : IDisposable { private readonly IHost server; public Uri ServerUri { get; } private readonly PactVerifier verifier; public ProviderTests(ITestOutputHelper output) { ServerUri = new Uri("http://localhost:7058"); server = Host.CreateDefaultBuilder() .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseUrls(ServerUri.ToString()); webBuilder.UseStartup<TestStartup>(); }) .Build(); server.Start(); this.verifier = new PactVerifier(new PactVerifierConfig { LogLevel = PactLogLevel.Debug, Outputters = new List<IOutput> { new XunitOutput(output) } }); } public void Dispose() { server.Dispose(); } [Fact] public void EnsureSomethingApiHonoursPactWithConsumer() { // Arrange string pactPath = Path.Combine("..", "..", "..", "..", "ConsumerTests", "pacts", "API Consumer.json"); try { this.verifier .ServiceProvider("Weather API", ServerUri) .WithFileSource(new FileInfo(pactPath)) .WithProviderStateUrl(new Uri(ServerUri, "/provider-states")) .Verify(); } catch (Exception e) { throw; } } }
排查方向
- 检查路由配置:确认
TestStartup中是否正确注册了/weather-forecast路径的控制器或端点,是否调用了app.UseRouting()和app.UseEndpoints(endpoints => endpoints.MapControllers())中间件。 - 确认服务启动状态:
server.Start()是同步调用,但服务可能未完全就绪,可添加短暂延迟(如await Task.Delay(1000))或通过健康检查接口确认服务可用后再执行验证。 - 核对pact文件请求路径:检查pact文件中定义的请求路径是否与Provider实际路由完全一致,避免拼写错误。
- 验证Provider状态接口影响:虽然使用硬编码数据,但确认
/provider-states接口的处理逻辑是否干扰了目标接口的正常响应。
内容的提问来源于stack exchange,提问作者Sienna
相关产品推荐
相关产品推荐

