Reputation: 574
I have an interface and implementation class, and I'm using dependency injection in the construction of the class:
class House {
public:
virtual void blah() = 0; //etc. all the interface of my class
};
class HouseImpl : public House {
protected:
Door *front_door;
public:
HouseImpl(Door *door) : front_door(door) {}
};
Now, in my factory method or whatever method is actually building the class and its dependencies, do I pass in the dependencies, or create them on the fly?
Option 1
By passing in the dependency, my unit test can use the same factory to pass in a mock object and my test is written against the interface only.
House* get_house(Door* door) {
return new HouseImpl(door);
}
void unit_test_method_on_house() {
MockDoor mock_door;
House* class_under_test = get_house(mock_door);
}
In this approach, I'm thinking of my factory method as basically an abstraction of the constructor for my interface type. The downside I see is that as I add dependencies, they all must be added to the factory method signature, which really just passes the buck to the clients.
Option 2
The alternative is that the factory is what is used in production code, and the unit test instantiates the implementation class directly.
House* get_house() {
return new HouseImpl(new Door()); //ignore the memmory management details here
}
void unit_test_method_on_house() {
MockDoor mock_door;
HouseImpl class_under_test(mock_door);
}
The concern here is that I'm opening the unit test up to depending on the implementation class rather than depending on the interface only. Obviously, I could just be very careful to write good tests that don't make implementation specific assumptions, but on a large project, I don't want to assume that all developers will be as careful as me.
So, what's the recommended approach here?
Upvotes: 1
Views: 281
Reputation: 2754
When you're unit testing, your unit test is inherently tightly coupled to the SUT. This is necessary in order for the unit test to be able to identify and limit the moving parts in the system. In other words, the unit test should work with the SUT class directly in order to differentiate it from any fake objects.
Another way of looking at it is that the test needs know the actual type of the SUT in order to know what to test. An interface focuses on the "what", while an implementation focuses on the "how". If the "how" changes, the unit test would likely need to be adjusted to account for it.
A factory object only exists to protect the consumer from being tightly coupled to the created objects. Since a unit test isn't worried about being tightly coupled to the SUT, it doesn't benefit from using a factory (it really would just get in the way).
Edit: You're right that you should still only test the SUT's public interface. If the unit test has too much knowledge of the inner workings of the SUT, then it becomes much harder to refactor the code without breaking the test.
The tight-coupling between a unit test and its SUT isn't just a liability, though; it can be used to your advantage. Using the interface as an access modifier, you can expose certain methods to the test that other consumers wouldn't necessarily see.
The SUT's constructor(s) fall under this category. For example, your factory method in Option 1 assumes that any house that is returned must use a door, but the factory method in Option 2 does not. The presence of a Door
is an implementation detail that may need to be hidden by the factory (e.g., an AdobeHut
implementation of House
wouldn't need a door).
If the test instantiates the SUT directly, you don't have to worry about these types of implementation details leaking into your design.
Upvotes: 4
Reputation: 37607
It is fine for your unit test to depend on the implementation so that it can construct an instance to test. That's what the unit test is for: testing that class. (Option 2.)
But you're right that the unit test should focus on whether the implementation implements the interface that it should. You can emphasize that by declaring the test instance to be the interface type, not the implementation type.
However, sometimes you'll also want to test what would otherwise be private methods that are tricky enough to need to be tested by themselves. In that case the test instance needs to be the implementation type.
I find the second kind of unit test to be much less common, so when I write one I just write a comment to make it clear that it's implementation-specific.
Upvotes: 2