DEV Community

max-arshinov
max-arshinov

Posted on • Updated on

Expression Trees + Web Application Factory = ❤️

Web Application Factory dramatically improves the developer experience. However, it can be even better. First of all, let’s check the example from the Microsoft website.

public class BasicTests 
    : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;

    public BasicTests(WebApplicationFactory<Program> factory)
    {
        _factory = factory;
    }

    [Fact]
    public async Task ReturnsSucces(string url)
    {
        // Arrange
        var client = _factory.CreateClient();
        var url = "about";


        // Act
        var response = await client.GetAsync(url);

        // Assert
        response.EnsureSuccessStatusCode();

        Assert.Equal("text/html; charset=utf-8", 
            response.Content.Headers.ContentType.ToString());
    }
}
Enter fullscreen mode Exit fullscreen mode

That’s cool. I can run high-level integration tests before deploying the code to an environment. This option reduces the efforts of detecting and fixing defects because integration tests can be run during the implementation phase, just before submitting a merge request.

efforts of detecting and fixing defects

Unfortunately, this example is far from being realistic. What we really want to test might look like this:

    [Theory]
    [ClassData(typeof(SophisticatedDataProvider))]
    public async Task ReturnsSucces(
        InputParams input, ExpectedOutput expectedOutput)
    {
        // Arrange
        var client = _factory.CreateClient();
        var url = "enpoint-with-a-lot-of-code-branches";


        // Act
        var content =
            CreatePostContentFromInputParams(input);
        var response = await client.PostAsync(url, content);

        // Assert
        response.EnsureSuccessStatusCode();

        var outputData = response
            .Content
            .ReadAsJsonAsync<OutputParams>();
        ComplexAssertions(expectedOutput, outputData);
    }
}
Enter fullscreen mode Exit fullscreen mode

Urls and input parameters are subject to change. Despite we have access to all the required .NET code through the assembly reference, which we need anyway to make the web application factory work, we have to do model binding and routing on our one in the test code. What if we could do something like:

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
        response.EnsureSuccessStatusCode(); 
        ComplexAssertions(expectedOutput, outputData);
    }
}
Enter fullscreen mode Exit fullscreen mode

If When routes or input/output parameters change developers would know about these issues first. This would reduce the efforts of detecting and fixing defects again by introducing compile-time safety for the test code. Luckily, the example above can perfectly work with help of Expression Trees. Moq uses exactly the same technique to configure mocks.

 var mock = new Mock<ILoveThisLibrary>();
 Mock
  .Setup(library => 
    library.DownloadExists("2.0.0.0")) // Check lambda type
 .Returns(true); // it's expression!
Enter fullscreen mode Exit fullscreen mode

You can find the complete source code of Controller Client on GitHub in case you are interested. Otherwise just install AspNetCore.Testing.Expressions via nuget and become an alpha tester.

Top comments (0)