How to unit test your Ninject dependencies

Marco Pierobon
2 min readApr 18, 2019

Many years ago developers started using inversion of control in their code and this helped decouple our code from its dependencies. When this is done right, it allows us to switch one dependency for another (provided they implement the same interface) without having to rewrite any code. This is sometimes implemented through interface registration in the config file and, at the cost of a higher difficulty for solving production issues, it allows us to toggle new features on or off at the cost of an app pool recycle or a service restart.

Using DI also introduces some complexity, as many applications tend to have a different DI registration context for the application and for the test (and sometimes different contexts depending on the type of tests — think testing over components that call external systems: sometimes you want to call them for real, other times you want to mock them and their dependencies).

This can lead the code to seem to work fine in our machine, until one dependency cannot be resolved in a deployed environment, like User Acceptance Test. When that happens, we have to checkout the same commit that was deployed, execute the same action that crashed the environment, add the missing dependency (dependencies), check in again, wait for the deploy to complete and possibly iterate when there were several missing ones.

Tired of this situation, I wrote a simple unit test that ensures all the dependencies are correctly resolved before the code is deployed. If the branch policy requires tests to run before the commit, the test guarantees that no code is committed without the dependencies properly registered.

What the code does is:

  • it load your main assembly (think of this as where your API resides)
  • It looks for a common set of classes to try to resolve (this could be the classes implementing ApiController). This could be simplified if you want to try to resolve all the classes in the assembly and not just a subset of them
  • It then proceeds to load the constructors from those classes, and for each dependency in there, it assesses whether it can be resolved. It if it can, the constructor for the resolved type is loaded and its dependencies are tried to be resolved (hence performing recursion). If not, the type is added to the list of types that cannot be resolved.

The recursive approach is due to the fact that the method CanResolve does not work down the DI recursive tree, so we need to travel the tree ourselves.

Happy coding!

--

--