dotnet / dotnet/aspnetcore

Controllers not loaded when using TestServer with UseStartup<T>(Func<WebHostBuilderContext, T> startupFactory)

Open
#49,887 0 comments 1 reaction 0 assignees View on GitHub
area-networking
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 6h
Merged PRs (30d)
290

Description

### Is there an existing issue for this?

- [X] I have searched the existing issues

### Describe the bug

**Description:**

When using **Microsoft.AspNetCore.TestHost.TestServer** in unit tests for an ASP.NET Core project, I have encountered unexpected behavior. In the test setup, I create a **TestServer** instance where the startup class is provided via a factory function through **WebHostBuilder.UseStartup(Func startupFactory)**.

In this configuration, my MVC controllers are not being properly loaded, resulting in 404 errors when trying to hit those controller routes. However, the same startup factory function works correctly when used within WebHostBuilder.ConfigureWebHostDefaults in the application's main entry point (Program.cs). In this latter scenario, the application runs as expected and all controllers are correctly loaded and reachable.

Interestingly, the ConfigureServices and Configure methods in the Startup class are still being called as expected during the unit tests.

Here's a simplified code snippet from my unit tests:

```
Func startupFactory = context =>
{
var mockService = new Mock();
var startup = new Startup(context.Configuration, mockService.Object);
return startup;
};

var server = new TestServer(new WebHostBuilder()
#if DEBUG
.UseEnvironment(Environments.Development)
#else
.UseEnvironment(Environments.Production)
#endif
.UseStartup(startupFactory));

var client = server.CreateClient();
```

**Workaround:**

To bypass this issue, I implemented a workaround by creating two constructors for the **Startup** class. The default constructor creates an instance of the default service, and the overloaded constructor accepts the service as a parameter, which can be a mock for unit tests:

```
public class Startup
{
private readonly IMyService myService;

public Startup(IConfiguration configuration)
: this(configuration, new TestMockMyService())
{
}

public Startup(IConfiguration configuration, IMyService myService)
{
// Startup logic...
}

public void ConfigureServices(IServiceCollection services)
{
// This method is being called as expected
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// This method is being called as expected
}
}
```
While this workaround allows me to inject mock services during unit tests, it seems less than ideal as it requires modifying the **Startup** class with additional constructors, which may not always be desirable or possible.

**Expected Behavior:**
I expect the **TestServer** to load my controllers correctly when a startup factory function is provided, just like it does when running the application.

**Actual Behavior:**
When running unit tests, the controllers are not being loaded, even though **ConfigureServices** and **Configure** methods are being called. However, when running the application, the controllers are correctly loaded.

### Expected Behavior

_No response_

### Steps To Reproduce

_No response_

### Exceptions (if any)

_No response_

### .NET Version

7.0.306

### Anything else?

Related

https://github.com/dotnet/aspnetcore/pull/24144
https://github.com/dotnet/aspnetcore/issues/19809
https://github.com/dotnet/aspnetcore/commit/113805aba8162ba7bac42943bcb1324801c20f46

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.