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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 05:19:50