Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using DI container in unit tests

We've been using Simple Injector with good success, in a fairly substantial application. We've been using constructor injection for all of our production classes, and configuring Simple Injector to populate everything, and everything's peachy.

We've not, though, used Simple Injector to manage the dependency trees for our unit tests. Instead, we've been new'ing up everything manually.

I just spent a couple of days working through a major refactoring, and nearly all of my time was in fixing these manually-constructed dependency trees in our unit tests.

This has me wondering - does anyone have any patterns they use to configure the dependency trees they use in unit tests? For us, at least, in our tests our dependency trees tend to be fairly simple, but there are a lot of them.

Anyone have a method they use to manage these?

like image 409
Jeff Dege Avatar asked Sep 15 '15 20:09

Jeff Dege


People also ask

How is dependency injection used in unit testing?

Dependency Injection is a way of injecting the dependencies into your class. Your class declares it's dependencies (e.g. via a constructor parameter) and whoever is using your class can provide those dependencies. This way we don't hard-code dependencies into our classes and our codebase is a lot more flexible.

How does dependency injection make testing easier?

By using dependency injection, you make creating test doubles (commonly called “mocks”) much more straightforward. If you pass dependencies to classes, it's quite simple to pass in a test double implementation. If dependencies are hard-coded, it's impossible to create test doubles for those dependencies.

Which injection method is better suited for unit testing while maintaining encapsulation?

Constructor injection is the more stable option. The objects can be immutable, the unit tests are clean to build and the Dependency Injection frameworks usually fail at start up to unknown arguments.


1 Answers

Refrain from using your DI container within your unit tests. In unit tests, you try to test one class or module in isolation, and there is little use for a DI container in that area.

Things are different with integration testing, since you want to test how the components in your system integrate and work together. In that case you often use your production DI configuration and swap out some of your services for fake services (e.g. a EmailService) but stick as close to the real thing as you can. In this case you would typically use your Container to resolve the whole object graph.

The desire to use a DI container in the unit tests as well, often stems from ineffective patterns. For instance, in case you try to create the class under test with all its dependencies in each test, you get lots of duplicated initialization code, and a little change in your class under test can in that case ripple through the system and require you to change dozens of unit tests. This obviously causes maintainability problems.

One pattern that helped me out here a lot in the past is the use of a simple SUT-specific factory method. This method centralizes the creation of the class under test and minimizes the amount of changes that need to be made when the dependencies of the class under test change. This is how such factory method could look like:

private ClassUnderTest CreateClassUnderTest(
    ILogger logger = null,
    IMailSender mailSender = null,
    IEventPublisher publisher = null)
{
    return new ClassUnderTest(
        logger ?? new FakeLogger(),
        mailSender ?? new FakeMailer(),
        publisher ?? new FakePublisher());
}

The factory method's arguments duplicate the class's constructor arguments, but makes them all optional. For any particular dependency that is not supplied by the caller, a new default fake implementation will be injected.

This typically works very well, because in most tests you are just interested in one or two dependencies. The other dependencies might be required for the class to function, but might not be interesting for that specific test. The factory method, therefore, allows you to only supply the dependencies that are interesting for the test at hand, while removing the noise of unused dependencies from the test method. As an example using the factory method, here's a test method:

public void Doing_something_will_always_log_a_message()
{
    // Arrange
    var logger = new ListLogger();

    ClassUnderTest sut = CreateClassUnderTest(logger: logger);

    // Act
    sut.DoSomething();
    
    // Arrange
    Assert.IsTrue(logger.Count > 0);    
}

If you are interested in learning how to write Readable, Trustworthy and Maintainable (RTM) tests, Roy Osherove's book The Art of Unit Testing (second edition) is an excellent read. This has helped me tremendously in my understanding of writing great unit tests. If you’re interested in a deep-dive into Dependency Injection and its related patterns, consider reading Dependency Injection Principles, Practices, and Patterns (which I co-authored).

like image 66
Steven Avatar answered Sep 22 '22 22:09

Steven