Web Application Factory VS Real HTTP Calls - Take Two
Published: 2023-03-11 15:51:45

I’d say that [Web-Application-Factory-based integration tests](https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-7.0)  are really close to the bottom border of the "integration" section of the [test pyramid](https://martinfowler.com/articles/practical-test-pyramid.html).

![Test Pyramid](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1aotfwbtjqp7274yf4e5.png)

You might want to have a good level of isolation to be able to run them as a part of the CI/CD pipeline and .NET provides many options such as:
- [Mocking services](https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-7.0#inject-mock-services) entirely;
- [Using EF Core In-Memory Database Provider or SQLite-InMemory](https://learn.microsoft.com/en-us/ef/core/testing/);
- [Using Docker](https://www.youtube.com/watch?v=8IRNC7qZBmk&ab_channel=NickChapsas) for tests.

By favouring isolation, however, we sacrifice the accuracy of testing. Issues related to the transport layer, third-party services, or DB can’t be found when using mocks because all of these are isolated from tests. On the other hand, the higher we climb the test pyramid the more expensive and fragile the tests become. Luckily, it’s possible to reuse a lot of code that we already have to build integration tests that would adjoin the upper side of the “integration tests” section of the pyramid.

Let’s say we have a `Post` controller method implemented somewhat like this:
```csharp
public async Task<PostResult> Post(InputParams input) {
    var valueFromDb = await GetValueFromDbAsync(input);
    var valueThirdPartyService = await
        GetValueFromThirdPartyServiceAsync(valueFromDb);

    return new PostResult(
        valueFromDb, valueThirdPartyService);
}
```

Both `GetValueFromDbAsync` and `GetValueFromThirdPartyServiceAsync` are isolated by [injecting mock services to the web application factory](https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-7.0#inject-mock-services). To make sure that the controller method works not only with mocks but with the real DB and third-party service we are going to implement a test that would call the real API endpoint deployed to the test environment.

The code of the web-application-factory-based test works just fine

```csharp
public class MyControllerTest:
    ControllerTestBase<MyController> 
{
    [Theory]
    [ClassData(typeof(SophisticatedDataProvider))]
    public async Task ReturnsSucces(
        InputParams input, ExpectedOutput expectedOutput)
    {
        // Arrange
        var client = _factory.CreateControllerClient();

        // Act
        var outputData = await client.SendAsync(
            (MyController c) => c.Post(input));

        // Assert
        ComplexAssertions(expectedOutput, outputData);
    }
}
```

except for this time we need the real HttpClient to do real calls to the real environment unlike the HttpClient implementation provided by the Web Application Factory.

## Applying Template Method Pattern
Let’s refactor the original test by applying [Template Method](https://refactoring.guru/design-patterns/template-method) pattern.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7xjm8klrdp2e0zy2v5ww.png)

```csharp
public abstract class HttpClientTestBase<T>
    where T: IHttpClientFactory
{
    protected readonly T HttpClientFactory;

    public HttpClientTestsBase(T http)
    {
        HttpClientFactory = http;
    }

    protected HttpClient CreateHttpClient(string name = "")
        => HttpClientFactory.CreateClient(name);
}

public class ControllerTestBase<
    TController,
    THttpClientFactory
>:
    HttpClientTestsBase<THttpClientFactory> 
    where THttpClientFactory : IHttpClientFactory
{
    public ControllerTestBase(THttpClientFactory http) :
        base(http) {}

    public ControllerClient<TController>
        CreateControllerClient(string name = "") => 
            new(CreateHttpClient(name));
}

public abstract class MyControllerTestBase:
    ControllerTestBase<MyController, TFactory> 
    IClassFixture<TFactory> 
    where TFactory : class, IHttpClientFactory
{
    protected MyControllerTestBase(THttpClientFactory http) :
        base(http) {}

    [Theory]
    [ClassData(typeof(SophisticatedDataProvider))]
    public async Task ReturnsSucces(
        InputParams input, ExpectedOutput expectedOutput)
    {
        // Arrange
        var client = CreateControllerClient();

        // Act
        var outputData = await client.SendAsync(
            (MyController c) => c.Post(input));

        // Assert
        ComplexAssertions(expectedOutput, outputData);
    }
```
Complete source code of [HttpClientTestBase](https://github.com/max-arshinov/AspNetCore.Testing.Expressions/blob/master/AspNetCore.Testing.MoqWebApplicationFactory/HttpClientTestsBase.cs) and [ControllerTestBase](https://github.com/max-arshinov/AspNetCore.Testing.Expressions/blob/master/AspNetCore.Testing.MoqWebApplicationFactory/ControllerTestsBase.cs) is  available on GitHub.

Albeit `CreateControllerClient` method is not abstract, it only delegates all work to `HttpClientFactory`, which in turn will be provided in the derived class. Thus, this is a variant of the “template method” implementation that uses composition instead of inheritance.

```csharp
public class MyControllerWafTest:
    MyControllerTestBase<MoqHttpClientFactory>
{
    MyControllerWafTest(MoqHttpClientFactory factory):
        base(factory){}
}

public class MyControllerHttpTest:
    MyControllerTestBase<HttpClientFactory>
{
    MyControllerWafTest(HttpClientFactory factory):
        base(factory){}
}
```

Test frameworks such as xUnit or NUnit will skip the base class since it’s abstract but will execute tests from both derived classes. As a result, we shared the test code between web-application-factory-based and real-http-client-based tests.

## Configuring Mocks

For the web-application-factory-based test we need mocks set up. Microsoft [recommends using ConfigureTestServices](https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-7.0#inject-mock-services ) to do so. That’s a nice and convenient way to override service registrations, but in order to keep as much code as possible shared between web-application-factory-based and real-http-client-based tests  we'll move mock registration from the test method to the class constructor.

```csharp
MyControllerWafTest:
    MyControllerTestBase<MoqHttpClientFactory>
{
    MyControllerWafTest(MoqHttpClientFactory factory):
        base(factory)
    {
        _factory.ConfigureMocks(nameof(ReturnsSucces), m => {
            m
               .Mock<ISomeRepository>
               .Setup(x => x.GetAll())
               .Returns(new [] {/*...*/});
                           
        });
    }
}

[Theory]
[ClassData(typeof(SophisticatedDataProvider))]
public async Task ReturnsSucces(
    InputParams input, ExpectedOutput expectedOutput)
{
    // Arrange
    var client =
        CreateControllerClient(nameof(ReturnsSucces));

    // Act
    var outputData = await client.SendAsync(
        (MyController c) => c.Post(input));

    // Assert
    ComplexAssertions(expectedOutput, outputData);
 }
```
The code above benefits from the [`IHttpClientFactory`](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-7.0) definition. Mocks for each named instance of `HttpClient` are configured independently. We only needed to update the client creation code to get the named HttpClient instance with mocks configured for this method:
`var client = CreateControllerClient(nameof(ReturnsSucces));`

## What if I don’t want to share some tests
Just don’t do it. Put tests that you want to run in both modes into the base class and add feel free to add additional tests to derived test classes when needed. The complete code can be found on [GitHub](https://github.com/max-arshinov/AspNetCore.Testing.Expressions). You can also use the [nuget package](https://www.nuget.org/packages/aspnetcore.testing.moqwebapplicationfactory) for you projects.

## A note on modular architecture
This type of tests is better aligned with [modular architecture](https://youtu.be/h_rBDIC51C4?t=1694).
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nduvh047ttt1fvwfh68a.png)