3

So I'm upgrading a bunch of our APIs and migrating them to the new shiny top-level statement style of startup.

These projects all also have integration tests that rely upon a WebApplicationFactory to create a TestClient for them. For the most-part, retargetting that at Program rather than Startup has worked just fine.

However, on one of my APIs, I just get 404s whenever I try to call a controller and I can't for the life of me work out why.

If I add in a minimal app.MapGet("test", () => "test") I can hit that from the test, so I guess for some reason, the controllers are getting unmapped. That being said, I'm struggling to see much difference between this API and the other ones.

So, all of my APIs follow the same basic pattern in Program:

var builder = WebApplication.CreateBuilder();

BuildConfiguration();
ConfigureLogging();
ConfigureXRay();
ConfigureServices();
ConfigureHost();

var app = builder.Build();
ConfigureApp();

app.Run();

I haven't included everything for brevity, but in ConfigureServices() I believe this line should add and configure all of the controllers:

void ConfigureServices()
{
    builder.Services.ConfigureHealthChecks();
    builder.Services.ConfigureAppMetrics();

    builder.Services.ConfigureFromAppSettingsConfig(builder.Configuration);
    builder.Services.ConfigureIOCServices(builder.Configuration);

    builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
    builder.Services.AddControllers(opt => opt.AddGlobalErrorHandling()); //<----- This one
    builder.Services.AddSoapCore();

    builder.Services.ConfigureSwagger();
}

Then in ConfigureApp() I've got this call which I believe should map the controllers:

    app.UseEndpoints(endpoints =>
                     {
                         endpoints.MapControllers();
                         endpoints.UseSoapEndpoint<ICorporateActionsSoapService>("/investments/api/CorporateActions.asmx", new BasicHttpBinding());
                     });

So if I run the app directly, all of that seems to work. For my tests, I've got the following.

public class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
    public CustomWebApplicationFactory()
    {
        SetAppSettingsValues();
        DisableMetricsBuiltFlag();
    }

    private void DisableMetricsBuiltFlag()
    {
        var metricsBuiltField = typeof(MetricsAspNetHostBuilderExtensions).GetField("_metricsBuilt",
                                                                                    BindingFlags.Static |
                                                                                    BindingFlags.NonPublic);
        metricsBuiltField?.SetValue(null, false);
    }

    private void SetAppSettingsValues()
    {
        Environment.SetEnvironmentVariable("IntegrationTests", "true");
    }
}

And then this is injected into my test classes as an IClassFixture<CustomWebApplicationFactory> by XUnit. I then replace a few of the services with mocks and create a test client.

public GlobalErrorHandlingIntegrationTest(CustomWebApplicationFactory factory)
{
    _mockLogger       = _fixture.Create<ILogger<CorporateActionsController>>();
    _mockAuditService = _fixture.Create<ICorporateActionsEmailAuditService>();

    _mockEmailService = _fixture.Create<ICorporateActionsEmailService>();
    _mockEmailService.GetEmails().Returns(Task.Run(() => _fixture.Create<CorporateActionResponse>()));
    _appFactory = factory
       .WithWebHostBuilder
           (
            builder =>
             {
                builder.ConfigureTestServices
                    (
                     x =>
                     {
                          x.Add(new ServiceDescriptor(typeof(ILogger<CorporateActionsController>), _mockLogger));
                          x.Add(new ServiceDescriptor(typeof(ICorporateActionsEmailService),       _mockEmailService));
                          x.Add(new ServiceDescriptor(typeof(ICorporateActionsEmailAuditService),  _mockAuditService));
                      }
                     );
             }
            );
   _testClient = _appFactory.CreateClient();
}

And then all of my tests using that _testClient just 404. Any idea what's going wrong here and why these controllers might be getting unmapped?


I've seen this question but the answer doesn't seem to apply to my case.

ScottishTapWater
  • 3,656
  • 4
  • 38
  • 81
  • Hi! Same exactly crazy behaviour here. Have you found any solution or workaround on this? – Ark667 Jul 04 '22 at 17:55
  • 1
    @Ark667 - Glad it's not just me... I actually couldn't work it out, ended up giving up on this one. The really weird thing is that it's working fine with almost identical code on my other services! – ScottishTapWater Jul 05 '22 at 10:24
  • 1
    @Ark667 - Can you check the answer below please? I've not got the project to check it against anymore – ScottishTapWater Jul 11 '22 at 13:26
  • I have used another aproach finally. I use docker containers for services, so I can run them easily and make the requests avoiding the WebApplicationFactory. But thank you so much for the answer, will try it if I face same problem in another project :D – Ark667 Jul 13 '22 at 13:13

1 Answers1

2

You have to pass the args to the WebApplication.CreateBuilder(args);

mrtaikandi
  • 6,753
  • 16
  • 62
  • 93
  • So I don't actually work at the company I had this issue with anymore, as such, I can't replicate it... If someone else can verify this, I'll mark it as confirmed :) – ScottishTapWater Jul 11 '22 at 13:26
  • After spending 2 hours in debugger i finally understood the problem (controller types not being located) and found this thread. And yes, my app was not passing args into builder. Passing args fixed the issue. Turns out, `WebApplicationFactory` passes `--applicationName=YourWebAppAssemblyName` via args and i think this is what causes controllers to load – Rast May 30 '23 at 00:09