Unfortunately, we must use a workaround to make the Program class discoverable when using minimal hosting. Let’s explore a few workarounds that leverage minimal APIs, allowing you to pick the one you prefer.
First workaround
The first workaround is to use any other class in the assembly as the TEntryPoint of WebApplicationFactory<TEntryPoint> instead of the Program or Startup class. This makes what WebApplicationFactory does a little less explicit, but that’s all. Since I tend to prefer readable code, I do not recommend this.
Second workaround
The second workaround is to add a line at the bottom of the Program.cs file (or anywhere else in the project) to change the autogenerated Program class visibility from internal to public. Here is the complete Program.cs file with that added line (highlighted):
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet(“/”, () => “Hello World!”);
app.Run();
public partial class Program { }
Then, the test cases are very similar to the ones of the classic web application explored previously. The only difference is the program itself, both programs don’t do the same thing.
namespace MyMinimalApiApp;
public class ProgramTest : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _httpClient;
public ProgramTest(
WebApplicationFactory<Program> webApplicationFactory)
{
_httpClient = webApplicationFactory.CreateClient();
}
public class Get : ProgramTest
{
public Get(WebApplicationFactory<Program> webApplicationFactory)
: base(webApplicationFactory) { }
[Fact]
public async Task Should_respond_a_status_200_OK()
{
// Act
var result = await _httpClient.GetAsync(“/”);
// Assert
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
}
[Fact]
public async Task Should_respond_hello_world()
{
// Act
var result = await _httpClient.GetAsync(“/”);
// Assert
var contentText = await result.Content.ReadAsStringAsync();
Assert.Equal(“Hello World!”, contentText);
}
}
}
The only change is the expected result as the endpoint returns the text/plain string Hello World! instead of a collection of strings serialized as JSON. The test cases would be identical if the two endpoints produced the same result.
Third workaround
The third workaround is to instantiate WebApplicationFactory manually instead of leveraging a fixture. We can use the Program class, which requires changing its visibility by adding the following line to the Program.cs file:
public partial class Program { }
However, instead of injecting the instance using the IClassFixture interface, we instantiate the factory manually. To ensure we dispose the WebApplicationFactory instance, we also implement the IAsyncDisposable interface.Here’s the complete example, which is very similar to the previous workaround:
namespace MyMinimalApiApp;
public class ProgramTestWithoutFixture : IAsyncDisposable
{
private readonly WebApplicationFactory<Program> _webApplicationFactory;
private readonly HttpClient _httpClient;
public ProgramTestWithoutFixture()
{
_webApplicationFactory = new WebApplicationFactory<Program>();
_httpClient = _webApplicationFactory.CreateClient();
}
public ValueTask DisposeAsync()
{
return ((IAsyncDisposable)_webApplicationFactory)
.DisposeAsync();
}
// Omitted nested Get class
}
I omitted the test cases in the preceding code block because they are the same as the previous workarounds. The full source code is available on GitHub: https://adpg.link/vzkr.
Using class fixtures is more performant since the factory and the server get created only once per test run instead of recreated for every test method.
Leave a Reply