Authorizing integration tests using AspNetCore TestServer & IdentityServer4

Kutlu Eren
4 min readJan 30, 2020

In the past days, my team has been working on implementing integration tests for a project. We have decided to use AspNetCore TestServer for this purpose which is very good because it provides a fully functioning in-memory web server using all the IOC and anything in your project. We have been using IdentityServer as well for authentication provider all along but a problem occured when authorizing api calls to in TestServer. How did we overcome it? Let’s see.

What is a TestServer?
AspNetCore introduces an in-memory test server called TestServer. It provides the ability to write integration tests without requiring a Web server. You can make HTTP requests to be validated by AspNet Core web app and it fully simulate clients. Here you can find easy implementation of TestServer and here you can find details about integration tests.

After initializing the test server, you can make API calls using its Http client. This simulates all the API calls from the clients with a fully HTTP request cycle. Here is a sample code;

var builder = new WebHostBuilder()
.UseEnvironment(“Development”)
.UseStartup<Startup>()
.UseApplicationInsights();
TestServer testServer = new TestServer(builder);HttpClient client = testServer.CreateClient();HttpResponseMessage response = await client.GetAsync(“/Index”);

So what is wrong? What are the odds of getting any problem?
After making an API call, we started getting Http 401 Unauthorized. That’s cool because the call was made without any token or any credentials. In order to get a token, another call had to be made to be authenticated with a username and password.

string AuthenticateURL = “api/Login/Authenticate”;
var dto = new AuthenticationModel { UserName = “admin”, Password = “1”};
var responseLogin = await client.PostAsync(AuthenticateURL, new JsonContent(dto));

After getting the token successfully, then it could be set as Authorization header and call API with the given token just like this;

client.SetBearerToken(responseLogin.Token);
HttpResponseMessage response = await client.GetAsync(“/Index”);

But still 401 when calling an endpoint in API!! What’s the matter?

Authorization issue and the solution
The problem occurs when the TestServer has both authentication and validation at the same time and for some reason it can not validate a token. Therefore a new TestServer for identity server has to be created and startup classes should be seperated from the real one, override some methods if needed.

First, I have seperated the TestStartup which inheriting the one used in project. The real startup class adds an identityserver to the pipeline but I had to remove it by overriding the method. It is a big step for the integration tests we have. It does not host any identity server. It also takes a HttpMessageHandler type as a parameter, we will get back to that later.

Then I have created a new startup class for identity server (IdentitiyServerStartup) but it is pretty much the same as the one we use in the project. Just few overrides in order to mock somethings. After that, a new TestServer was created using IdentitiyServerStartup class.

var identityServerWebHostBuilder = new WebHostBuilder()
.UseStartup<IdentitiyServerStartup>()
.ConfigureServices(
services =>
{
services.AddSingleton(AppSettings);
})
.ConfigureTestServices(services =>
{
services.AddSingleton(AppSettings)
.AddSingleton(typeof(IHttpClientHelper), this.IdentityClientHelper)
.AddTransient<IIsmContextFactory>(serviceProvider => { return contextFactory; })
.AddTransient<ITypedFactory>(serviceProvider =>
{
return new TypedFactoryTestHelper(services);
});
})
.UseDefaultServiceProvider(x => x.ValidateScopes = false)
.UseKestrel();
IdentityServerProxy = new IdentityServerProxy(identityServerWebHostBuilder);

IdentityServerProxy class is a proxy class used to act as a proxy really. You might want to check its awesome implementations here. Then here is the solution, we have to create a HttpMessageHandler from the proxy’s client and give it to the TestServer which we make the API calls. Here is the implementation ;

Server = new TestServer(new WebHostBuilder()
.UseStartup<TestStartup>()
.ConfigureServices(services =>
{
services.AddSingleton(AppSettings)
.AddSingleton(IdentityServerProxy.IdentityServer.CreateHandler());
})
.ConfigureTestServices(services =>
{
services.AddSingleton(AppSettings)
.AddSingleton(typeof(IHttpClientHelper), this.ClientHelper)
.AddTransient<IIsmContextFactory>(serviceProvider => { return contextFactory; })
.AddTransient<ITypedFactory>(serviceProvider =>
{
return new TypedFactoryTestHelper(services);
});
})
.UseDefaultServiceProvider(x => x.ValidateScopes = false)
.UseKestrel());

This message handler acts as a gateway to identity server thus the token gets validated by identity server. Unfortunately I can not publish our code base but I have implemented a very similar simple code and here and you can check it out. I hope it helps who are struggling in such case. I would love to hear some feedbacks.

Some references

https://stackoverflow.com/questions/39390339/integration-testing-with-in-memory-identityserver#55573671

https://stackoverflow.com/questions/55590928/building-an-integration-test-for-an-aspnetcore-api-that-uses-identityserver-4-fo/55598091?noredirect=1#comment105854154_55598091

https://github.com/entertainment-database/Etdb.UserService.AspNetCore/blob/fea18d2e6d693efeac1752a29b741e357bb2510b/test/Etdb.UserService.Bootstrap.Tests/Startups/ApiServerStartup.cs#L62

https://github.com/IdentityServer/IdentityServer4/tree/master/src/IdentityServer4/test/IdentityServer.IntegrationTests

https://github.com/simax/SuperSimpleAPI/pull/1

--

--