Search code examples
c#.nettestingpact-net

PactNet contract test failing with HttpRequestException and SocketException


I'm trying to write a contract test using PactNet for the following method:

public async Task<IEnumerable<Models.RefData.Instrument> GetInstruments(string issuerCountry, string instrumentType)
{
    ValidateNotNullOrWhiteSpaceParameter(issuerCountry, nameof(issuerCountry));
    ValidateNotNullOrWhiteSpaceParameter(instrumentType, nameof(instrumentType)); ;

    var queryString = $"instruments?issuerCountry={HttpUtility.UrlEncode(issuerCountry)}&instrumentType={HttpUtility.UrlEncode(instrumentType)}";

    int pageNo = 0;
    int pageSize = 20;

    _logger.LogDebug($"GetInstruments Request:{queryString}");

    var httpResponseMessage = await _client.GetAsync(queryString + $"&page={pageNo}&size={pageSize}");

    _logger.LogDebug($"GetInstruments Response Status Code:{httpResponseMessage.StatusCode}");

    switch (httpResponseMessage.StatusCode)
    {
        case HttpStatusCode.OK:
        var content = await httpResponseMessage.Content.ReadAsStringAsync();
        var result = JsonConvert.DeserializeObject<GetInstrumentsResponse>(content);
                    
        // if there are no results, return Empty
        if (result.Metadata.TotalElements == 0)
        {
            return Enumerable.Empty<Models.RefData.Instrument>();
        }

        var instruments = new List<Models.RefData.Instrument>();
        instruments.AddRange(result.Embedded.Instruments);
        for (pageNo = 1; pageNo < result.Metadata.TotalPages; pageNo++)
        {
            var innerQueryString = queryString + $"&page={pageNo}&size={pageSize}";

            _logger.LogDebug($"GetInstruments Request Additional Page:{innerQueryString}");

            var httpResponseMessage2 = await _client.GetAsync(innerQueryString);
            if (httpResponseMessage2.StatusCode != HttpStatusCode.OK)
            {
                _logger.LogError($"The requested page number {pageNo} gets response error {httpResponseMessage2.StatusCode.ToString()}.");
                throw new UnexpectedResponseException(httpResponseMessage.StatusCode);
            }
                var content2 = await httpResponseMessage2.Content.ReadAsStringAsync();
                var result2 = JsonConvert.DeserializeObject<GetInstrumentsResponse>(content2);

                if (result2.Embedded.Instruments != null && result2.Embedded.Instruments.Any())
                {
                    instruments.AddRange(result2.Embedded.Instruments);
                }
            }

            if (instruments.Count != result.Metadata.TotalElements)
            {
                _logger.LogWarning($"Total number of instruments is different from MetaData.  MetaData states {result.Metadata.TotalElements}, however only {instruments.Count} instruments retrieved.");
        }

        _logger.LogDebug($"GetInstruments Result:{instruments.ToJson()}");

        return instruments;
        default:
            throw new UnexpectedResponseException(httpResponseMessage.StatusCode);
    }
}

I created the following ConsumerPactTests.cs and ConsumerPactClassFixture.cs using this as a guide.

public class ConsumerPactTests : IClassFixture<ConsumerPactClassFixture>
{
    private IMockProviderService _mockProviderService;
    private string _mockProviderServiceBaseUri;

    public ConsumerPactTests(ConsumerPactClassFixture fixture)
    {
        _mockProviderService = fixture.MockProviderService;
        _mockProviderService.ClearInteractions(); //NOTE: Clears any previously registered interactions before the test is run
        _mockProviderServiceBaseUri = fixture.MockProviderServiceBaseUri;
    }

    [Fact]
    public void ItHandlesInvalidDateParam()
    {
        // Arange
        var invalidRequestMessage = "issuerCountry or instrumentType is not valid";
        _mockProviderService.Given("There is data")
            .UponReceiving("A invalid GET request for Date Validation with invalid date parameter")
            .With(new ProviderServiceRequest
            {
                Method = HttpVerb.Get,
                Path = "/api/v2",
                Query = "issuerCountry=USA&instrumentType=foo"
            })
            .WillRespondWith(new ProviderServiceResponse
            {
                Status = 400,
                Headers = new Dictionary<string, object>
                {
                    { "Content-Type", "application/json; charset=utf-8" }
                },
                Body = new
                {
                    message = invalidRequestMessage
                }
            });

        // Act
        RefDataHttpService sut = new RefDataHttpServiceBuilder().Build();
        var result = sut.GetInstruments("USA", "foo").GetAwaiter().GetResult();
        var resultBodyText = result.GetEnumerator();

        // Assert
        Assert.NotNull(resultBodyText);
    }
}



public class ConsumerPactClassFixture : IDisposable
{
    public IPactBuilder PactBuilder { get; private set; }
    public IMockProviderService MockProviderService { get; private set; }

    public int MockServerPort { get { return 9222; } }
    public string MockProviderServiceBaseUri { get { return String.Format("http://localhost:{0}", MockServerPort); } }

   public ConsumerPactClassFixture()
   {
       var pactConfig = new PactConfig
       {
           SpecificationVersion = "2.0.0",
           PactDir = @"..\..\..\..\..\pacts",
           LogDir = @".\pact_logs"
       };

       PactBuilder = new PactBuilder(pactConfig);

       PactBuilder.ServiceConsumer("Consumer")
           .HasPactWith("Provider");

       MockProviderService = PactBuilder.MockService(MockServerPort);
    }

    #region IDisposable Support
    private bool disposedValue = false; // To detect redundant calls

    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                // This will save the pact file once finished.
                PactBuilder.Build();
            }

            disposedValue = true;
        }
    }

    // This code added to correctly implement the disposable pattern.
    public void Dispose()
    {
        // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
        Dispose(true);
    }
    #endregion
}

When I run my test I get this error:

dotnet test --filter "FullyQualifiedName=Bond.Publisher.Tests.ContractTest.ConsumerPactTests.ItHandlesInvalidDateParam"

Test run for c:\Workspace\prod\test\Bond.Publisher.Tests\bin\Debug\netcoreapp3.1\Bond.Publisher.Tests.dll(.NETCoreApp,Version=v3.1) Microsoft (R) Test Execution Command Line Tool Version 16.7.0 Copyright (c) Microsoft Corporation. All rights reserved.

Starting test execution, please wait...

A total of 1 test files matched the specified pattern. [xUnit.net 00:00:10.95] Bond.Publisher.Tests.ContractTest.ConsumerPactTests.ItHandlesInvalidDateParam [FAIL] X Bond.Publisher.Tests.ContractTest.ConsumerPactTests.ItHandlesInvalidDateParam [4s 196ms] Error Message: System.Net.Http.HttpRequestException : No connection could be made because the target machine actively refused it. ---- System.Net.Sockets.SocketException : No connection could be made because the target machine actively refused it. Stack Trace: at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean allowHttp2, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken) at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) at Bond.Publisher.HttpMessageHandlers.UnoAuthorisationHeaderMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in c:\Workspace\usprod\src\Bond.Publisher\HttpMessageHandlers\UnoAuthorisationHeaderMessageHandler.cs:line 37 at System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts) at Bond.Publisher.Services.RefDataHttpService.GetInstruments(String issuerCountry, String instrumentType) in c:\Workspace\prod\src\Bond.Publisher\Services\RefDataHttpService.cs:line 52 at Bond.Publisher.Tests.ContractTest.ConsumerPactTests.ItHandlesInvalidDateParam() in c:\Workspace\prod\test\Bond.Publisher.Tests\ContractTest\ConsumerPactTests.cs:line 52 ----- Inner Stack Trace ----- at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken)

Test Run Failed.

I suspect it may be some sort of authentication issue as UnoAuthorisationHeaderMessageHandler.cs deals with that. What have I done wrong with this?


Solution

  • For me the path was too long. When I moved the project to a folder closer to C:\ the test ran.