Click here to Skip to main content
14,874,239 members
Articles / Test automation
Article
Posted 13 May 2020

Stats

5.3K views
1 bookmarked

Integration Testing: More Fixtures than AutoFixture

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
13 May 2020CPOL4 min read
An explanation of the concept of a Fixture and how it can be used for unit testing and integration testing
The concept of a fixture as used in AutoFixture for unit tests can be used for other kinds of tests too. This article explains how and gives an example for integration tests.

Introduction

Fixtures as used in AutoFixture have been shown to be a really useful concept for test automation. Fixtures are not just useful for unit testing, but the concept is reusable in integration testing too, In this article, an explanation is given of what a Fixture is and how the concept can be used for both unit testing and integration testing. Fixtures always have one thing in common: they manage the "arrange" part of your test for you, which is useful for all kinds of tests, not just unit tests.

Background

It will be really helpful you have some experience with TDD of .NET Core applications, including mocking and preferably with .NET Core 3.1 as used here. Having experience with Fixtures is useful too but not needed. The examples shown in this article are based on xUnit but the concepts and techniques can be applied for other unit testing frameworks too.

Using the Code

At first, we have a look at the code we want to test. Here it is:

C#
[Route("api/[controller]")]
[ApiController]
public class SearchEngineController : ControllerBase
{
   private readonly ISearchEngineService _searchEngineService;

   public SearchEngineController(ISearchEngineService searchEngineService)
   {
       _searchEngineService = searchEngineService;
   }

   [HttpGet("{queryEntry}", Name = "GetNumberOfCharacters")]
   public async Task<ActionResult<int>> GetNumberOfCharacters(string queryEntry)
   {
       var numberOfCharacters = 
           await _searchEngineService.GetNumberOfCharactersFromSearchQuery(queryEntry);
       return Ok(numberOfCharacters);
   }
}

As becomes clear from the code above, it is just a controller method that calls some interface method and returns the result with the OK status code (200). Here is the implementation of that interface.

C#
public class SearchEngineService : ISearchEngineService
{
   private readonly HttpClient _httpClient;

   public SearchEngineService(HttpClient httpClient)
   {
       _httpClient = httpClient;
   }

   public async Task<int> GetNumberOfCharactersFromSearchQuery(string toSearchFor)
   {
       var result = await _httpClient.GetAsync($"/search?q={toSearchFor}");
       var content = await result.Content.ReadAsStringAsync();
       return content.Length;
   }
}

This code is also really simple. A web request is done and the length of the result is returned.

Here is how the dependencies are added in the Startup class:

C#
public void ConfigureServices(IServiceCollection services)
{
   services.AddControllers();
   var googleLocation = Configuration["Google"];
   services.AddHttpClient<ISearchEngineService, SearchEngineService>(c =>
            c.BaseAddress = new Uri(googleLocation))
            .SetHandlerLifetime(TimeSpan.FromMinutes(5))
            .AddPolicyHandler(GetRetryPolicy());
}

What becomes clear now is that there are two dependencies we need to be able to mock:

  • An internal dependency: This is the ISearchEngineService instance injected into the controller.
  • An external dependency: This is the search engine (url) set as a base address of the HttpClient added. For integration testing, we need to mock that since we have no influence on whether it gives the same response and will remain online.

Mocking of internal dependencies is often done for unit testing purposes, with Moq as explained in this article. Mocking of external dependencies is often done for integration testing purposes, with WireMock as explained in this article, where some of the code presented here comes from. All code shown here is available on GitHub too.

This is how a unit test with a Fixture looks like:

C#
[Fact]
public async Task GetTest()
{
   // arrange
   var fixture = new Fixture().Customize(new AutoMoqCustomization());
   var service = fixture.Freeze<Mock<ISearchEngineService>>();
   service.Setup(a => a.GetNumberOfCharactersFromSearchQuery(It.IsNotNull<string>()))
                .ReturnsAsync(8);
   var controller = fixture.Build<SearchEngineController>().OmitAutoProperties().Create();

   // act
   var response = await controller.GetNumberOfCharacters("Hoi");

   // assert
   Assert.Equal(8, ((OkObjectResult)response.Result).Value);
   service.Verify(s => s.GetNumberOfCharactersFromSearchQuery("Hoi"), Times.Once);
}

What we see here, is the following:

  1. The first step is the creation of the fixture.
  2. After that, the Freeze method is called (before the Create method) to specify the internal dependency we want to do some verifications on at the end of the test.
  3. The internal dependency needs to be setup with Setup method.
  4. In the assert section of the test method, we verify the calls to the internal dependency (the service instance).

Implementing such as test, requires a NuGet package: AutoFixture.AutoMoq.

Since recent versions of .NET Core, integration testing has been made much easier. The main difference with unit testing is that integration is more real (and therefore less configurable). The Startup class and Program class are covered. Real internal dependencies are used instead of mocks/stubs. Only external dependencies require mocking since these can be offline, have changing behaviour or be unstable, which all should not break the tests. Here is how to do integration testing with a fixture:

C#
[Fact]
public async Task GetTest()
{
   // arrange
   using (var fixture = new Fixture<Startup>())
   {
      using (var mockServer = fixture.FreezeServer("Google"))
      {
          SetupStableServer(mockServer, "Response");
          var controller = fixture.Create<SearchEngineController>();

          // act
          var response = await controller.GetNumberOfCharacters("Hoi");

          // assert
          var request = mockServer.LogEntries.Select(a => a.RequestMessage).Single();
          Assert.Contains("Hoi", request.RawQuery);
          Assert.Equal(8, ((OkObjectResult)response.Result).Value);
       }
    }
}

private void SetupStableServer(FluentMockServer fluentMockServer, string response)
{
    fluentMockServer.Given(Request.Create().UsingGet())
    .RespondWith(Response.Create().WithBody(response, encoding: Encoding.UTF8)
    .WithStatusCode(HttpStatusCode.OK));
}

What we see here, is similar:

  1. The first step is the creation of the Fixture using the Startup class to initialize all dependencies once the Create method is called.
  2. After that, the FreezeServer method is called (before the Create method) to setup the external dependency we want to do some verifications on at the end of the test.
  3. The internal dependency needs to be setup with a self written SetupStableServer method describing that a certain request requires a certain response. This is explained in more detail here.
  4. In the assert section of the test method, we verify the web request sent to the external dependency (the mocked service).

Implementing such as test, requires another NuGet package: ConnectingApps.IntegrationFixture.

As becomes clear from the code above, integration testing with a fixture is done in basically the same way as unit testing with a fixture. However, instead of arranging and verifying internal dependencies, this needs to be done with external dependencies. This sounds obvious but when setting up integration tests without using a Fixture, a lot of boiler plate is required as explained here.

Points of Interest

What became clear to me when studying fixtures, working with fixtures and writing this article is that the concept of a fixture can be used in various ways. AutoFixture is well known for its usefulness when writing the arrange part of unit tests but fixtures can be useful for (the arrange part of) other kinds of tests too. It saves us as developers from writing a lot of boilerplate code.

History

  • 13th May, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Daan Acohen
Software Developer (Senior)
Netherlands Netherlands
I am a self-employed software engineer working on .NET Core. I love TDD.

Comments and Discussions

 
-- There are no messages in this forum --