Integration testing for dotnet core APIs: Handling 3rd party service calls using wiremock

Article Banner
Author(s): Ajay Kumar
Published On: 08 Mar 2025

Summary


This is a continuation of the Integration testing in dotnet series. This time we will be covering the scenario where third-party service calls are present in our API flows.

Before we begin further, you may want to visit the previous articles in this series to get a better context of integration testing in general and the code repository that we will be using for demo purposes here. The links to the articles are mentioned below:

Getting started

Integration testing for dotnet core APIs: Introduction

devcodex.in
Blog Image
Demo with database

Integration testing for dotnet core APIs: Handling database

devcodex.in
Blog Image

Also, all the code used here is available at: SuperHero repository link

Why need to mock?


Mocking third-party service calls is crucial to ensure tests are isolated, reliable, and repeatable.

It avoids dependency on external systems that may be unavailable, slow, or unpredictable, allowing the focus to remain on verifying the behavior of your application in a controlled environment.

Mocking also simplifies setting up specific scenarios, such as handling failures or edge cases, which might be hard to replicate with real services.

However, unlike unit tests, the mocking here is wire-level mocking since we want all our logic, including the way we call the HTTP service and the way we parse the response, because here we are testing the overall integration of all the components of our application. So we will be mocking only what goes out to the network and what comes into the network here.

Suspects API


Continuing with the article, let's imagine we need an API that returns the list of notorious suspects in the superhero universe.

We created an API as mentioned in the below Swagger screenshot:

Suspect API screenshot
Figure 2 : Suspect API screenshot

            [HttpGet("/suspects")]
            public async Task<IActionResult> Suspects([FromQuery] string searchTerm)
            {
            var people = await GetPeople();
            var peopleOfInterest = people.Data.Where(person =>
            person.First_Name.Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase) ||
            person.Last_Name.Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase) ||
            person.Email.Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase));
            if (!peopleOfInterest.Any())
            {
            return NotFound();
            }
            return Ok(peopleOfInterest);
            }

            // Method to fetch people from 3rd party service
            // We have also defined a variable "SuspectServiceUrl" in appsettings.json
            private async Task<PersonResponse> GetPeople()
            {
            using var client = new HttpClient();

            var url = $"{configuration.GetValue<string>("SuspectServiceUrl")}/api/users?page=1";
            var response = await client.GetAsync(url);
            if (!response.IsSuccessStatusCode)
            {
            throw new HttpRequestException($"Unable to get people from {url}");
            }

            return await response.Content.ReadFromJsonAsync<PersonResponse>();
            }
        
Code Sample #2 : Suspect API

Wiremock to the rescue!


Here we will be setting up Wiremock to be used in the integration tests. For those who are new to Wiremock, please visit WireMock.Net for more details.

Install the below package in the Integration test project:


            dotnet add package WireMock.Net --version 1.6.10
        
Code Sample #3 : Install WireMock.Net

After setup, the shared fixture class will look as follows. I have provided comments to understand the code context better.

On a high level, we did the following:

  • Created and initialized the Wiremock server.
  • Captured the mocked server’s base URL in the property SuspectServiceUrlOverride.
  • Exposed the mocked server via the property WireMockServer. This is needed because our application can behave differently based on different responses from the third-party service.

            public class SharedFixture : IAsyncLifetime
            {
            public string SuspectServiceUrlOverride { get; private set; } = null!;
            private WireMockServer? _server;

            public WireMockServer WireMockServer => _server;

            public async Task InitializeAsync()
            {
            SuspectServiceUrlOverride = StartWireMockForService();
            }

            public async Task DisposeAsync()
            {
            _server?.Stop();
            }

            private string StartWireMockForService()
            {
            _server = WireMockServer.Start();
            return _server.Urls[0];
            }
            }
        
Code Sample #4 : SharedFixture class

Next, we guide our application to override the SuspectServiceUrl from appsettings.json and take the base URL value for the third-party service from the mocked server, i.e., SharedFixture’s SuspectServiceUrlOverride property.

For this, we make changes in our CustomApiFactory class as shown below:


            public class CustomApiFactory(SharedFixture sharedFixture) : WebApplicationFactory<Program>
            {
            protected override void ConfigureWebHost(IWebHostBuilder builder)
            {
            builder.ConfigureAppConfiguration((_, configBuilder) =>
            {
            configBuilder.AddInMemoryCollection(new Dictionary<string, string>
            {
            ["SuspectServiceUrl"] = sharedFixture.SuspectServiceUrlOverride
            });
            });
            }
            }
        
Code Sample #5 : CustomApiFactory class

This completes the setup. We are now ready to write the tests.

Test Scenarios


Below are the test scenarios for our Suspect API. These tests ensure that the API behaves as expected under different conditions, including both positive and negative cases.


            /// <summary>
            /// This method will take params and will return the list of people/suspects
            /// </summary>
            /// <param name="pageNum">Number of page to look for. Can be any number, but for this problem, lets
            assume this will always be 1</param>
            /// <param name="apiResponse">Response JSON returned by the API</param>
            /// <param name="expectedStatusCode">Status code returned from the API</param>
            /// <typeparam name="T">Type of the response</typeparam>
            private void SetupServiceMockForSuspectApi<T>(string pageNum, T apiResponse, HttpStatusCode
            expectedStatusCode = HttpStatusCode.OK)
            {
            factory.SharedFixture.WireMockServer
            .Given(Request
            .Create()
            .WithPath("/api/users")
            .UsingGet()
            .WithParam("page", MatchBehaviour.AcceptOnMatch, ignoreCase: true, pageNum))
            .RespondWith(Response
            .Create()
            .WithStatusCode(expectedStatusCode)
            .WithBodyAsJson(apiResponse, Encoding.UTF8));
            }
        
Code Sample #6 : SetupServiceMockForSuspectApi method


            [Fact(DisplayName = "Get suspects should return all matching suspects")]
            public async Task Get_All_Suspects_Returns_List_Of_Matching_Suspects()
            {
            // Arrange
            // Setting up mocked data for success response
            SetupServiceMockForSuspectApi("1", new PersonResponse()
            {
            Data =
            [
            new Suspect()
            {
            Id = 1,
            First_Name = "Selina",
            Last_Name = "Kyle",
            Email = "selina.kyle@gotham.com",
            }
            ]
            });

            // Act
            var response = await factory.CreateClient().GetAsync("/suspects?searchTerm=selina");

            // Assert
            response.StatusCode.Should().Be(HttpStatusCode.OK);
            var superHeroes = await response.Content.ReadFromJsonAsync<List<Suspect>>();
            superHeroes.Should().NotBeEmpty();
            superHeroes![0].Id.Should().Be(1);
            superHeroes![0].Email.Should().Be("selina.kyle@gotham.com");
            superHeroes![0].First_Name.Should().Be("Selina");
            superHeroes![0].Last_Name.Should().Be("Kyle");
            }
        
Code Sample #7 : Positive Test: Valid Data


            [Fact(DisplayName = "Get suspects should return 500 status code when API responds with 500 status code")]
            public async Task Get_All_Suspects_Should_Return_500_StatusCode_When_3rd_Party_Api_Fails()
            {
            // Arrange
            // Setting up mocked data for failure response
            SetupServiceMockForSuspectApi("1", new {Status = "Failed" }, HttpStatusCode.InternalServerError);

            // Act
            var response = await factory.CreateClient().GetAsync("/suspects?searchTerm=selina");

            // Assert
            response.StatusCode.Should().Be(HttpStatusCode.InternalServerError);
            }
        
Code Sample #8 : Negative Test: 500 Response

Test Results


When running the tests, we can observe the following results:

Test results showing successful execution of all test cases
Figure 3 : Test results showing successful execution of all test cases

As seen in the above screenshot, all the test cases have passed successfully, ensuring that our API behaves as expected under different scenarios, including both positive and negative cases.

Thats about it for this article. Hope you liked it.

For reference, the code repository being discussed is available at github: https://github.com/ajaysskumar/SuperHeroSolution

Thanks for reading through. Please share feedback, if any, in comments or on my email ajay.a338@gmail.com

Copyright © 2025 Dev Codex

An unhandled error has occurred. Reload 🗙