Unit Testing for ASP.NET Core Dependency Injection

How do you test an entire service collection in a row?

Tiago Araújo
.Net Programming
4 min readNov 24, 2020

--

Photo by Mark Fletcher-Brown on Unsplash

Once upon a time…

As you all know, when we start to develop an ASP.NET Core application we will have 1, 2, or 3 services that quickly create 5 or 10 unit tests to validate if all the services are registered… And then, Commit, Push, Merge, Done. Easy peasy lemon squeezy!

But then, the next day you will most likely create another unit test, next week it will probably be necessary to delete two of them and create a third one, and so on. On the other hand, at the same time, you notice your buddy “forgets” to create a unit test for some service he created. What does this mean? Yes, that’s correct, too many problems ahead. 😓

Well, I know you certainly have integration tests and/or automated tests that will catch any mistake. Right? Hmm, no. I pretty sure most of you don’t have a gorgeous testing pyramid. In fact, I would say that what we have is a slice of swiss smelly cheese with a lot of holes 🙄 which is actually pretty good! (the cheese, not the coverage 🧀😋).

I have been working on an application that has approx. 700 services, hundreds of endpoints, several merge requests, +100 developers coding on a daily basis, low code coverage, zero integration tests, and zero automated tests 🔥😈. The result? Merges with badly registered dependencies.

One day I decided to develop a unit test to validate the entire service collection. Can you guess the asserted result? FAIL!

How to automate, Step by Step

First, we need a ServiceCollection with our entire list of dependencies. You must include everything you can. In the example, I have included all the ASP.NET Core dependencies and my application dependencies from the MyAppStartup. Then, we need to create a WebHost application to extract the ServiceCollection.

Note that theMyAppStartup is the real startup application. The EmptyStartup is an empty startup necessary just for the WebHostBuilder to register and start an application. The key is to create theMyAppStartup class in the ConfigureAppConfiguration method and use it in the ConfigureServices method to extract the entire list from your service collection after configuring the services.

❗ ️To initialize the IConfiguration with a JSON file is not required. It’s just an example if you want to make decisions about the service collection loading based on the application settings. Don’t forget these cases and make sure you create different scenarios for Production, Staging andDevelopment environments.

Let’s Test!

Fortunately, the Microsoft.Extensions.DependencyInjection package has an extension BuildServiceProvider from theIServiceCollection with all the configurations that can do the hard work. One of these options is theValidateOnBuild which deeply validates its dependencies and if something goes wrong the builder will throw an exception. In fact, it throws an AggregateException that aggregates the exceptions for each invalid service.

How to add Controllers and more

There are more objects and services besides the controllers and view components that haven’t been registered. It depends on the application configuration (MVC, Endpoints, RazorPages, etc.)

If you need to add more objects to the service collection and validate your dependencies on the service provider build, use reflection and LINQ to search, filter, and add, as you can see below.

Beyond the constructor, parameter actions can be bind with services using the attribute[FromServices]. The code below can be used to look for those attributes and register them as a dependency of a somewhat object. The DependencyService<T> class is a fake dependent just to be registered on the service collection.

How to add Middlewares

Middlewares can have dependencies to be resolved in the constructor and the InvokeAsync method. Using the same strategy as in the controllers, you can search the Invoke method in the Middleware classes. Here, I consider middleware class an object whose name contains the word middleware or subclasses of the IMiddleware interface.

Challenge: Discover dependencies from HttpContext.RequestServices calls

I tried to use reflection and a Roslyn analyzer to discover HttpContext.RequestServices calls, but nothing with enough confidence for a production unit test. I think this version is a good MVP. If you have any suggestions please contact me and we can certainly work together. Further, I will explore and try to write about it. 🙂

Outcome

You can use the unit test below to validate all your service dependencies, but I must advise you: it won’t kill all of your service registration problems although it will prevent most of them. Use your imagination and development skills to add more corner cases. Save time by writing less unit tests, and you will go to the production with more confidence. I do believe you (me) will be a better programmer, otherwise, you would not be reading those last words.

--

--

Tiago Araújo
.Net Programming

SOFTWARE ENGINEER to make money — Listen to MUSIC for energy — RIDE for no reason to the heart — SHARE with loved ones — The DREAM: the last 3 in the same box