Following the introduction on this series on a previous post, we will now talk about dependency injection and how it has the effect of allowing for more testable code. Sometimes when I talk about this concept it is difficult to explain the effect that applying it might have on the tests. For that reason I think it is better to demonstrate with a near-real-world situation.
Obviously, keep in mind this is not real code, so don't worry about the design or implementation details that don't contribute to the point being discussed.
As you can see, it is simple. There's a class called ShipManager (what else?) that receives position updates for the ships. It keeps the last position reported from each ship and does some calculation to see how much the ship moved. It assigns some values to the update and finally it persists the final version of the update.
Since I want to keep this minimal, the tests in this series will be a matter of asserting a few conditions and printing true or false. This is not production code, but it provides a low barrier of entry for everyone.
Here is the example of a test:
I am instantiating the ShipPersistenceService and DistanceCalculator in the constructor of ShipManager on purpose. This makes it very difficult to build a unit test that tests a behavior in isolation, because then the ShipPersistenceService would need to have a database - or other persistence medium - set up in order to run the test. Also, how would I be able to get the distance that the calculator returned? Maybe use the fact that it mutates the update itself? That's crappy, but this kind of problem comes up very often in codebases. Here is the test that we will make possible:
Notice that both ShipPersistenceService and DistanceCalculator are used by the ShipManager in the PositionReceived method, so we could say that ShipManager depends on them to do the job. These two classes are dependencies that are being created in the constructor, but doing that is preventing us from isolating the ShipManager logic from it's dependencies.
Wouldn't it be good if we could just swap in fake dependencies? Then when we run the tests we wouldn't have to deal with the database. What if we could... like... inject dependencies? Yeah, that's an incredibly original idea. So we make a simple change:
Now we are using proper Dependency Injection. Notice that this does not yet achieve our objective, but we now are required to pass the dependencies as a parameter when creating a ShipManager in the test. We have control of those dependencies in the test, but not yet how they behave. We can easily change that using inheritance by marking the methods as virtual and then overriding the behavior in a test implementation that we then inject. And thus is born the mock. And what are mocks? They are fake implementations of the dependencies and serve the sole purpose of helping us testing. Here are the mocks for this case:
Notice that the mocks add a little something of their own. The ShipPersistenceServiceMock is recording the last ShipPositionUpdate it was called with. This will allow us to read it in the test. The DistanceCalculatorMock allows us to set the DistanceToReturn which is returned to the ShipManager. Let's see how to use these new capabilities for the test:
The fact that we can simply set the values we want the methods to return and also record the parameters is a really nice property we will be using A LOT. Almost every single unit test makes use of these features.
For the tests, I find it better to just create the dependencies by hand and inject directly. This allows for easy to read tests that are also self contained without messing with IOC containers. Remember, your unit tests should be dead simple without too much of a hassle to run.
As for the way of using mocks, there are a lot of ways of doing this. Given the small context of the article, using inheritance was the easiest one to demonstrate. The most common way is to define interfaces for your dependencies and then depend on those interfaces instead of depending on the classes directly. You would then register these interfaces with your IOC container and define which specific type should be injected.
So this is it for this article on the From crappy to Happy series. Next up we will be dealing with a very common problem: that DateTime.UtcNow that I ignored can have some weird effects on the tests!
Obviously, keep in mind this is not real code, so don't worry about the design or implementation details that don't contribute to the point being discussed.
The code
As you can see, it is simple. There's a class called ShipManager (what else?) that receives position updates for the ships. It keeps the last position reported from each ship and does some calculation to see how much the ship moved. It assigns some values to the update and finally it persists the final version of the update.
How do we start testing?
When you think about it, tests are dead simple. A test either passes or it doesn't. I'm going to focus in unit tests here, and we could use a lot of frameworks like NUnit, XUnit, etc. Sure, we can introduce the whole plethora of tools that exist at the whim of a single command, but what is the simplest thing we can do to test our code? Check if true or false!Since I want to keep this minimal, the tests in this series will be a matter of asserting a few conditions and printing true or false. This is not production code, but it provides a low barrier of entry for everyone.
Here is the example of a test:
Questions I want to answer with tests
Looking at how the code behaves, there are some immediate tests that we could write:- Is the distance calculated correctly from the previous position?
- Does the manager persist the position?
- Does the manager correctly calculate the expiration date for the position?
Why is the code not testable?
There are multiple kinds of tests. We will focus on unit tests. These kinds of tests tend to be small, isolated and test a single behavior only.I am instantiating the ShipPersistenceService and DistanceCalculator in the constructor of ShipManager on purpose. This makes it very difficult to build a unit test that tests a behavior in isolation, because then the ShipPersistenceService would need to have a database - or other persistence medium - set up in order to run the test. Also, how would I be able to get the distance that the calculator returned? Maybe use the fact that it mutates the update itself? That's crappy, but this kind of problem comes up very often in codebases. Here is the test that we will make possible:
Notice that both ShipPersistenceService and DistanceCalculator are used by the ShipManager in the PositionReceived method, so we could say that ShipManager depends on them to do the job. These two classes are dependencies that are being created in the constructor, but doing that is preventing us from isolating the ShipManager logic from it's dependencies.
Wouldn't it be good if we could just swap in fake dependencies? Then when we run the tests we wouldn't have to deal with the database. What if we could... like... inject dependencies? Yeah, that's an incredibly original idea. So we make a simple change:
Now we are using proper Dependency Injection. Notice that this does not yet achieve our objective, but we now are required to pass the dependencies as a parameter when creating a ShipManager in the test. We have control of those dependencies in the test, but not yet how they behave. We can easily change that using inheritance by marking the methods as virtual and then overriding the behavior in a test implementation that we then inject. And thus is born the mock. And what are mocks? They are fake implementations of the dependencies and serve the sole purpose of helping us testing. Here are the mocks for this case:
Notice that the mocks add a little something of their own. The ShipPersistenceServiceMock is recording the last ShipPositionUpdate it was called with. This will allow us to read it in the test. The DistanceCalculatorMock allows us to set the DistanceToReturn which is returned to the ShipManager. Let's see how to use these new capabilities for the test:
The fact that we can simply set the values we want the methods to return and also record the parameters is a really nice property we will be using A LOT. Almost every single unit test makes use of these features.
Who creates the dependencies now?
Since we are moving towards dependency injection now, who is responsible for creating he dependencies for each class? That is the topic for a whole other post, but the basic answer is: not the class that depends on them. You want to move the instantiation up. Once you start using this pattern you would normally start using in Inversion of Control Containers such as Unity or Ninject. There you can define the graph of your dependencies and assign scopes to each class. That is widely out of scope though.For the tests, I find it better to just create the dependencies by hand and inject directly. This allows for easy to read tests that are also self contained without messing with IOC containers. Remember, your unit tests should be dead simple without too much of a hassle to run.
Recap
We applied the concept of dependency injection to code that had some dependencies. Coupled with some minor changes and the creation of mock objects, we were able to transform untestable code into testable. This technique is generally easy to implement, but on real code some issues will arise because the dependency graph is much more complex.As for the way of using mocks, there are a lot of ways of doing this. Given the small context of the article, using inheritance was the easiest one to demonstrate. The most common way is to define interfaces for your dependencies and then depend on those interfaces instead of depending on the classes directly. You would then register these interfaces with your IOC container and define which specific type should be injected.
So this is it for this article on the From crappy to Happy series. Next up we will be dealing with a very common problem: that DateTime.UtcNow that I ignored can have some weird effects on the tests!
Comments
Post a Comment