I encourage you to take a look at the commit history of your DI bootstrappers; I bet they change a lot. Wouldn't it be nice to have a set of tests that proves that the dependency container still behaves like you expect it to at runtime? Next to proving correctness, I think writing these tests also helps you discover various behaviours of your DI container, which is a valuable investment in itself.
Let me show you a few tests that I wrote to put my ASP.NET MVC Ninject bootstrapper under test.
I started by opening up the Ninject bootstrapper, making the CreateKernel method public.
public static IKernel CreateKernel() { var kernel = new StandardKernel(); kernel.Bind<IEntryService>().To<EntryService>(); return kernel; }In the test class, I used the TestInitialize attribute to initialize a new instance of the kernel before every test. I'm not sure this is really necessary, but I want to avoid that my tests experience side-effects of a previous test.
[TestInitialize] public void Setup() { _kernel = NinjectMVC3.CreateKernel(); }Needless to say, the first test should prove that an implementation of the IEntryService interface can be resolved. The behaviour I observed while playing with this case, is that Ninject throws a Ninject.ActivationException when the implementation can't be resolved.
Ninject.ActivationException: Error activating IEntryServiceSo to test whether an implementation can be resolved, I just make sure no exceptions are thrown on resolving the dependency.
No matching bindings are available, and the type is not self-bindable.
Activation path:
Request for IEntryService
Suggestions:
- Ensure that you have defined a binding for IEntryService.
- If the binding was defined in a module, ensure that the module has been loaded into the kernel.
- Ensure you have not accidentally created more than one kernel.
- If you are using constructor arguments, ensure that the parameter name matches the constructors parameter name.
- If you are using automatic module loading, ensure the search path and filters are correct.
[TestMethod] public void Test_IEntryService_Can_Be_Resolved() { AssertDoesNotThrowWhenResolved<IEntryService>(); }The AssertDoesNotThrowWhenResolved method is a helper method which tries to resolve a dependency of T and asserts that no exceptions are thrown while doing so. The assertion is borrowed from Xunit (package available on Nuget).
private void AssertDoesNotThrowWhenResolved<T>() { Xunit.Assert.DoesNotThrow(() => _kernel.Get<T>()); }A second useful test is testing the lifetime of the resolved implementation. For most of my dependencies, I expect a new instance every time they are resolved. This test looks like this.
[TestMethod] public void Test_IEntryService_Is_New_Instance() { AssertNewInstanceIsResolved<IEntryService>(); }The AssertNewInstanceIsResolved method is another helper method which resolves two instances of T and asserts they are not the same.
private void AssertNewInstanceIsResolved<T>() { var instance = _kernel.Get<T>(); var secondInstance = _kernel.Get<T>(); Assert.AreNotSame(instance, secondInstance); }That's it. While these tests are very cheap to write, they do provide great value. I can imagine testing more complex bindings, like contextual bindings, taking a bit more time to set up, but the value of these tests increases proportionally.
Do you put your DI bootstrappers under test? If you don't, why not?
No comments:
Post a Comment