Leveraging WebApplicationFactory with .NET Core Minimal APIs

Published on

What are Minimal APIs?

Without getting too much into the weeds, the new .NET Core minimal APIs provide a succinct way to define your API using reduced files, dependencies, and boilerplate. To learn more about them, check out the Minimal APIs Overview on Microsoft's documentation website or explore the tutorial for a more hands-on approach. Love them or hate them, the minimal APIs are now the default when using dotnet new. Consequently, I'd encourage you to learn and engage with them-- you're likely to encounter them in the future.

The Concern

Notably, minimal APIs don't require an explicit Program class declaration nor a Startup. For those familiar with WebApplicationFactory, you'll remember that it takes a generic type argument TEntryPoint. In most cases I've seen, the type used for the entry point is either Startup or Program. Does that mean WebApplicationFactory is busted? Fear not. Today, we'll explore two options I'm aware of for testing your Minimal API.

InternalsVisibleTo As a Means to See Program

Under the hood, Program is generated implicitly with internal accessibility. Knowing this, a simple workaround for making a Program type accessible to your WebApplicationFactory is simply by adding an InternalsVisibleTo tag to your service's csproj file. Your csproj might look like the following:

<Project Sdk="Microsoft.NET.Sdk.Web">

    <PropertyGroup>
        <TargetFramework>net6.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
    </PropertyGroup>
    
    <ItemGroup>
        <InternalsVisibleTo Include="Farmer.Codes.MinimalApis.WebApplicationFactoryDemo.InternalsVisibleToTests" />
    </ItemGroup>

</Project>

And your WebApplicationFactory<TEntry> might look like:

using Microsoft.AspNetCore.Mvc.Testing;

namespace Farmer.Codes.MinimalApis.WebApplicationFactoryDemo.InternalsVisibleToTests;

internal class TestApplication : WebApplicationFactory<Program>
{
    // You'd override methods as appropriate for your test needs here.
}

Caveats

This solution is referenced by the Microsoft documentation (see the tutorial link above), so it's reasonable to consider it an acceptable solution. However, exposing internals to your assembly might not be desirable for whatever reason. Your testing philosophy might be such that you should only be testing public contracts and one could argue whether or not this violates that. I'll avoid making a judgement call in this post.

Using an Alternative Class as a Proxy to your Assembly

Despite the common practice of using either Startup or Program as the TEntryPoint argument in your application, it's not at all necessary to be one of those. In fact, Microsoft's documentation states:

The WebApplicationFactory<TEntryPoint> will find the entry point class of TEntryPoint assembly and initialize the application by calling IWebHostBuilder CreateWebHostBuilder(string [] args) on TEntryPoint.

This constructor will infer the application content root path by searching for a WebApplicationFactoryContentRootAttribute on the assembly containing the functional tests with a key equal to the TEntryPoint assembly FullName. In case an attribute with the right key can't be found, WebApplicationFactory<TEntryPoint> will fall back to searching for a solution file (*.sln) and then appending TEntryPoint assembly name to the solution directory. The application root directory will be used to discover views and content files.

The application assemblies will be loaded from the dependency context of the assembly containing TEntryPoint. This means that project dependencies of the assembly containing TEntryPoint will be loaded as application assemblies.

What this means in practice is that we can actually specify a TEntryPoint that is just a class belonging to the same assembly. For instance, you could create an empty AssemblyHint class in the service project or reference another class present.

Your AssemblyHint implementation might look like:

namespace Farmer.Codes.MinimalApis.WebApplicationFactoryDemo;

/// <summary>
/// This class is used as a hint to WebApplicationFactory{TEntryPoint}.
/// </summary>
public class AssemblyHint
{
    
}

Your WebApplicationFactory<TEntry> might look like this:

using Microsoft.AspNetCore.Mvc.Testing;

namespace Farmer.Codes.MinimalApis.WebApplicationFactoryDemo.AssemblyHintTests;

public class TestApplication : WebApplicationFactory<AssemblyHint>
{
    // You'd override methods as appropriate for your test needs here.
}

An example of using an existing class can be seen on Scott Hansleman's blog, where he casually demonstrates this same behavior explained above.

Conclusion

At the end of the day, both methods achieve the same thing. While I don't yet regularly take advantage of the minimal APIs, if I were to I think I'd take the second approach as I prefer the explicitness as well as avoiding unnecessarily exposing internal classes. Ultimately, you should choose the path you think best fits with your team, workflow, etc.

You can take a look at the full code from this post on GitHub.

© 2021-2022 Austin Farmer