Skip to content

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

@nickhoeferpickpro

Description

@nickhoeferpickpro

Is there an existing issue for this?

  • 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<WebHostBuilderContext, Startup> 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<WebHostBuilderContext, Startup> startupFactory = context =>
{
    var mockService = new Mock<IMyService>();
    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<Startup>(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

#24144
#19809
113805a

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-networkingIncludes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions