We've been having a lot of discussion in our company about how to test the domain and we've learned a lot from the discussion that I thought was worth sharing.
My general view is that testing your domain classes against their public interfaces using state testing is a valid choice. The way we have chosen do this is:
- Only use real domain objects in the tests, no mocking.
- In general test against the public interfaces of public domain classes.
Where it does get interesting is where we are using state (as in state pattern) based validation in our domain objects. Nilsson summarizes the problem:
"In reality, adding rules also creates problems with testing. You nee to setup instances to correct states. You can write test helpers (such as private methods) for dealing with that, but it's still a hindrance."
The solution we've gone for up till now is an ObjectMother based approach, so for instance we have a CustomerObjectMother than can produce Customers in all sorts of different states. So if you are testing an Order and you need a Customer in the Active state then you can call into the CustomerObjectMother to get it. The object mother classes have all sorts of different creation/attachment methods.
This approach is definitely not without its flaws, including:
- The domain should be enforcing/encapsulating quite a lot so testing it can be more complex than testing the individual (encapsulated) classes within the domain.
- If we modify the Customer code and break it then its possible lots tests will break because they use the Customer class in the SUT setup phase (e.g we need a Customer even though we're testing the Order).
The main advantages of the approach are:
- Tests at the public interface level mean that we can refactor within the domain safely because they act as a good safety net.
- They point out places where the design is a bit overly complex (e.g. if we have lots of Customer states then the ObjectMother has to handle them all).
We've decided to have two levels of tests within the domain, we haven't got good names but essentially they will be:
- Domain Tests - Testing at the public interface with real objects, mainly state testing.
- Interation Tests - Detailed white box tests going right into the internal classes within the domain, mocking and doing interaction testing as appropriate.
This seems a good compromise, you are testing the public interface so all us TDDers can refactor happily below the public interface level but we've got detailed tests for the smaller classes.
The next question is how many of each test to have, hopefully good coverage by both and a lot of reuse but we'll have to see how that goes. Good.
However we have to decide how to mock the domain classes. I'm not at all in favor of covering my domain classes in interfaces, some people do it but there are valid reasons not to not least that the interfaces we put in for mocking hide the real meaningful abstractions within the domain.
Plus in this case we'll often be mocking classes internal to the domain (as in accessibility of internal in .NET). We can do this because we use InternalsVisibleToAttribute to give our unit tests access to the internals of the domain but do we really want to design our internal classes so that they are more easily mockable, then use depdency injection.
An example might help here, lets say our Customer class uses a custom collection called CustomerAddressCollection. This collection has complex logic to track the temporal nature of addresses and the fact that addresses have different purposes (home/work). Users of the Customer don't even know about this custom collection though as we've encapsulated its very existence.
We've tested CustomerAddressCollection but we want to test that Customer works correctly with it, and in this case we want to write an interaction/mocking test. It seems to me we have two choices:
- Give CustomerAddressCollection an interface then allow it to be injected into Customer and use Rhino Mocks for the mocking.
- Use TypeMock to mock CustomerAddressCollection, mock the methods that we expect Customer to access and then check Customer accesses it correctly.
For now I'm thinking the second option is better.