SamV
SamV

Reputation: 194

Override Startup in WebApplicationFactory does not map endpoints

I have configured an xUnit project to test an Identity Server implementation. I have created a TestStartup class which inherits Startup and overrides my Identity Server implementation for testing purposes. The problem is when I call my TestStartup using my custom WebApplicationFactory, my endpoints are not mapped. If I call Startup from my custom WebApplicationFactory, my endpoints are mapped.

Startup.cs

protected readonly IConfiguration _configuration;
protected readonly IWebHostEnvironment _webHostEnvironment;

public Startup(IConfiguration configuration, IWebHostEnvironment environment)
{
    _configuration = configuration;
    _webHostEnvironment = environment;
}

public virtual void ConfigureServices(IServiceCollection services)
{
    ///code goes here
    ConfigureIdentityServices(services);            
}

/// <summary>
/// Split into its own method so we can override for testing
/// </summary>
/// <param name="services"></param>
public virtual void ConfigureIdentityServices(IServiceCollection services)
{
   ///code goes here
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseIdentityServer();
    app.UseRouting();

    //app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

TestStartup.cs

public TestStartup(IConfiguration configuration, IWebHostEnvironment environment) : base(configuration, environment)
{

}

public override void ConfigureServices(IServiceCollection services)
{
    base.ConfigureServices(services);
}

/// <summary>
/// In memory identity database implementation for testing
/// </summary>
/// <param name="services"></param>
public override void ConfigureIdentityServices(IServiceCollection services)
{
    //test implementation
}

public override void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    base.Configure(app, env);          
}

Custom WebApplicationFactory

public class ApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
    protected override IWebHostBuilder CreateWebHostBuilder()
    {
        return WebHost.CreateDefaultBuilder(null).UseEnvironment("Development")
                    .UseStartup<TStartup>();
    }
}

Controller Test Class

public class UserControllerTests : IClassFixture<ApplicationFactory<Startup>>
{
    private readonly ApplicationFactory<Startup> _factory;

    public UserControllerTests(ApplicationFactory<Startup> factory)
    {
        _factory = factory;
    }

    [Fact]
    public async Task Get_Users()
    {
        var client = _factory.CreateClient();

        var result = await client.GetAsync("/user/users");
        result.StatusCode.Should().Be(HttpStatusCode.OK);
    }

}

When I run my controller test, if inject TestStartup instead of Startup, I don't get any endpoints returned from the endpoint routing, even though I call the base.Configure(app,env) method from my TestStartup class.

Upvotes: 4

Views: 2327

Answers (1)

Henrik Laursen
Henrik Laursen

Reputation: 136

I have the exact same issue. Using a subclassed startup in my custom WebApplicationFactory subclass results in no endpoints being registered whereas UseStartup regsiters them. Odd thing is that I have another API project (without Identity) where using the subclassed startup actually registers endpoints. Tried to comment everything out in startup, except services.AddControllers and app.UseRouting/app.UseEndpoints in the Identity project and it still doesn't work.

Edit: Found the solution. Apparently services.AddControllers only registers routes in the executing assembly, which in the subclassed integrationtest scenario is the test assembly. By adding the required controllers as application parts, the routing system picks up the routes:

services.AddControllers()
    .AddApplicationPart(typeof(<controllername>).Assembly);

things will work.

Upvotes: 12

Related Questions