Monday, March 30, 2020

ASP.NET Core - test controller creation

The default dependency injection container in ASP.NET Core is pretty decent but lacks some of the features of the more seasoned containers like Castle Windsor.
One of the things that's sorely lacking, in my opinion, is being able to validate whether the dependency graph is complete. This used to be one of the first tests I add to a .NET Framework app when using a dependency injection container.
It's quite common for devs to break the dependency graph while evolving the application and it can be a costly mistake. The build will not break but the application will be broken.

A partial fix : validate controller creation

Within ASP.NET Core the entry point for any external request is a controler, so if we ensure the dependency graph is complete for all controllers we'll be able to catch a good portion of mistakes.
One of the nice things that ASP.NET Core brings to the table is the in-memory TestServer, designed specifically to enable integration testing. Testing startup configuration is most definitely an integration test.
Using that test server you can bootstrap your application and run tests against the dependency container.
The Gist below has all the code for a working test.

Test project setup

For this code to execute, you should setup an XUnit test project, either from Visual Studio or from the command line:

dotnet new xunit -o MyProject.Tests

Then add the following Nuget packages
  • Microsoft.AspNetCore.Mvc.Testing
  • FluentAssertions
Finally, add a reference to your ASP.NET Core web application project. The Startup class is the startup class for your application. If needed you can provide appsettings.json to give your application the required configuration settings.

The test code

The test uses the WebApplicationFactory provided by the ASP.NET Core for the TestServer (check the Microsoft docs for more info).
The constructor will receive the factory and allow you to configure it as needed. The test method itself uses XUnit's Theory to execute once for each controller. The controllers are discovered using reflection. Depending on your setup, you may need to use a different strategy to find the controllers in your application.
Finally, the test itself does some magic to get a hold of the ServiceProvider and tries to create each controller.

The error message will be nice and verbose so you know which controller could not be created.