Integration testing in Asp.Net Core 6

Viewed 455

I am trying to implement integration tests in Asp.Net Core 6. I am trying to use a different database for testing as well as overriding endpoint authentication using the AllowAnonymousFilter. I am following this turorial.

The goal is to override the "ConnectionStrings" property in appsettings.json in the main project with the contents in integrationsettings.json. The contents from integrationtests.json shows up in api.Configuration.Providers[0].Data, so reading of that file seems to work. The actual appsettings.json is formatted the same way but with real data. However, the test still runs with the standard database.

The AllowAnonymousFilter does not work. Since this endpoint requires authorization I get a 401 code. I am new to dotnet and I don't really know how to troubleshoot this...

Test:

public class UserControllerIntegrationTests
    {    

        [Fact]
        public async Task Get_ReturnsUserFromDbAsync()
        {
            var api = new ApiWebApplicationFactory();
            var client = api.CreateClient();
            var response = await client.GetStringAsync("api/user?id=08da26ac-9de8-4cd8-8ca9-ce21f4952a6d");

            var result = JsonConvert.DeserializeObject<UserDto>(response);

            Assert.Equal("FirstName", result.FirstName);
        }        
    }

ApiWebApplicationFactory:

internal class ApiWebApplicationFactory : WebApplicationFactory<Program>
    {
        public IConfiguration Configuration { get;  private set; }
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureAppConfiguration(config => 
            {
                Configuration = new ConfigurationBuilder()
                    .AddJsonFile("integrationsettings.json")
                    .Build();

                config.AddConfiguration(Configuration);
            });

            builder.ConfigureTestServices(services =>
            {
                MvcServiceCollectionExtensions.AddMvc(services, options => options.Filters.Add(new AllowAnonymousFilter()));
            });
        }
    }

integrationsettings.json:

{
  "ConnectionStrings": {
    "Database": "Server=testserver;Database=testserver;User=testserver;Password=testserver;"
  }
}
1 Answers

TLDR; services.AddMvc(options => options.Filters.Clear());

But.

You need to clear the filters you already added before adding the allow anonymous. On the other hand, if you clear the filters, you will not need the allow anonymous.

You can clear the filters like this in builder.ConfigureTestServices :

services.AddMvc(options => options.Filters.Clear());

I would recommend you do not use this approach though. It would be much better to test that your filter actually works by writing tests to make sure your endpoints actually return 401/403 when an anonymous user calls them.

I would recommend you set up an integrationtest user that you can use to call the API successfully. We usually set up a simple client credentials user when we are using the Bearer auth scheme and OpenIDConnect. Hopefully you can find a way to set up a user that suits your environment.

When we use a client credentials user, we can simply create a custom DelegatingHandler that we use in our tests. It typically looks like this:

public class BearerHttpMessageHandler : DelegatingHandler
{
    private string _scope { get; set; }
    private readonly ITokenAcquisition _tokenAcquisition;

    public void SetScope(string scope) => _scope = scope;

    public BearerHttpMessageHandler(ITokenAcquisition tokenAcquisition)
    {
        _tokenAcquisition = tokenAcquisition;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        var accessToken = await _tokenAcquisition.GetAccessTokenForAppAsync(_scope,
            authenticationScheme: JwtBearerDefaults.AuthenticationScheme);

        request.Headers.Authorization =
            new AuthenticationHeaderValue(JwtBearerDefaults.AuthenticationScheme, accessToken);

        return await base.SendAsync(request, cancellationToken);
    }
}

The handler is added to the DI container and can be used like this:

var handler = scope.ServiceProvider.GetService<BearerHttpMessageHandler>();
var authenticatedClient = Factory.CreateDefaultClient(handler);

You can read more about protecting api's here: https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-web-api-call-api-overview

Related