Should I mock a Domain Service?
- It depends.
- Providing test doubles that stand in for domain services will often be a good idea.
- Providing mocks that stand in for domain services may be a good idea.
What follows will make more sense if you are familiar with the different flavors of test doubles; Mocks Aren't Stubs is an accessible starting point.
Using test doubles is a trade off. There are risks associated with the fact that the system under test isn't talking to a real collaborator, and there are benefits. Part of our craft is understanding the trade we are making.
There are properties that we want our tests to have.
- We want the tests to be fast, so that running them is less of a distraction.
- We want the tests to be isolated, so that we can run them in parallel, and save even more wall clock time.
- We want the tests to be simple -- fewer lines of code in the test suite means fewer bugs in the test suite
- We want the tests to be comprehensible -- we want most of the code to be describing the test, rather than describing a bunch of scaffolding
- We want the tests to be stable -- if we don't make any changes, a second run of a test should always give us the same result as the first
- We want the tests to be accurate -- a failing test should always indicate a mistake.
But all of those are just bikewash unless
- The tests call our attention to mistakes that we make.
Replacing real collaborators (which tend to be messy) with fake collaborators (which tend to be simple) increases the probability that we miss certain categories of mistakes; so we had better be sure when we do that that the benefits we gain offset the increased risks.
We derive almost no additional benefit from mocking a value. Well designed value objects are already well isolated, side effect free, and tend to express the semantics of a test better than a substitute would. They live entirely within the functional core of your application.
If you run that same math on entities, you will see that it doesn't make much sense to mock an entity either.
With domain services, however, the trade offs start to look really interesting. Domain services are the mechanism by which an encapsulated part of the domain model communicates with its collaborators; those collaborators might be other parts of the same model, or they may be further away.
When Evans described domain services in the blue book, he included among the motivating examples needing to access application and infrastructure services -- code that lives outside of the abstraction boundary of the domain model. Domain services are often proxies for communicating side effects, which may even cross process boundaries.
Mock across architecturally significant boundaries, but not within those boundaries.
Domain services are often proxies for code across an architecturally significant boundary.
So if you've got a domain service that is an in memory abstraction -- Orders needs access to an in memory tax calculator service, then the test double doesn't provide nearly as much marginal benefit as a test double that stands in for a domain service that needs to talk to a database....
Put another way, there are a lot of different things under the umbrella term "domain service", and they have different trade offs.
You are much more likely to use a test double when the actual behavior of the service is hard to predict, or hard to constrain.
IOfferCalculator.CalculateValue is a simple method in this case - it does not connect to a database and it does not call any other methods.
That sounds like the marginal advantage of introducing a mock is small; so I would recommend using the real implementation in this circumstance.