0
votes

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?

2

2 Answers

0
votes

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

0
votes

The System.Net.Sockets.SocketException : No connection could be made because the target machine actively refused it. usually happens when there is no server listening on the url you are sending.

Check if the ruby service is up and running (the test runner starts it), you should see it in Task Manager under Visual Studio

Alernatively, before you call pactBuilder.Build(), you should be able to do an HTTP request through PostMan to http://localhost:9222/instruments...