1

I am trying to decide whether to introduce mocks in my isolated Domain Model tests. I have a class method similar to this:

public Offer AssignOffer(OfferType offerType, IOfferValueCalculator valueCalculator) 
        { 
            DateTime dateExpiring = offerType.CalculateExpirationDate(); 
            int value = valueCalculator.CalculateValue(this, offerType); 
            var offer = new Offer(this, offerType, dateExpiring, value); 
            _assignedOffers.Add(offer); 
            NumberOfActiveOffers++; 
            return offer; 
        } 

which I took from here: https://github.com/jbogard/presentations/blob/master/WickedDomainModels/After/Model/Member.cs

I have now read this article: http://enterprisecraftsmanship.com/2016/06/15/pragmatic-unit-testing/ and this article: http://www.taimila.com/blog/ddd-and-testing-strategy/. They both seem to suggest that I should not mock OfferType (as it is a Value Object). However my question is: should I be mocking IOfferValueCalculator (a Domain Service)? IOfferValueCalculator does not sit in the innermost layer of the Onion, however it does sit in the Domain Model (second most inner layer of the Onion).

The reason I ask is because all these articles specifically reference Entities and Value Objects (advising against mocking them), however they do not reference Domain Services.

  • 1
    I don't see how it being a domain service changes whether you should mock it or not. The whole point of unit tests, interfaces and mocks is to isolate the unit under test from the rest of the system, so which layer your class under test resides in doesn't seem to be a relevant factor to me. – Robert Harvey Jan 25 '18 at 0:50
  • Whether mock domain service and value object are really a team taste and based on how you define your SUT. If you SUT is on aggregate root level, then value object belong to the aggregate and domain service only serves for this aggregate should not be mocked. Ideally, we need multi-level of unittests. The bottom level is test-per-class (mock all dependencies), then test-per-aggregate (less mocks). Then goes service level, etc. Finally, we reach the test pyramid. – ivenxu Jan 25 '18 at 0:52
  • I'm not sure what you want to test here. Every line is something that can be tested individually. Do we want to test that the integer and collection size agree? This seems like it's mostly construction code not behavior code. What would this do to make a test fail? – candied_orange Jan 25 '18 at 5:17
  • @Robert Harvey, the reason I have specified Domain Services is because all the articles I have read (links posted() specifically reference Entities and Value Objects only. I have a Domain Service and I am not sure what to do with it. – w0051977 Jan 25 '18 at 8:18
  • 1
    @w0051977 What I'm saying is that you'd have to assume some specific stuff to justify mocking it and even then it's a trade off. It's worth understanding that stuff rather than turning this into an automatic rule. I'll think about an answer but I'm kinda sleepy. So for now, g'night. – candied_orange Jan 25 '18 at 10:47
6

Should I mock a Domain Service?

Short answers

  1. It depends.
  2. Providing test doubles that stand in for domain services will often be a good idea.
  3. 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.

Longer answers:

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.

  • Bikewash? Is this a typo or is my vocabulary about to expand? – candied_orange Jan 25 '18 at 5:13
  • IOfferCalculator.CalculateValue is a simple method in this case - it does not connect to a database and it does not call any other methods. Therefore I think your "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" applies i.e. a mock/stub is not needed. Do you agree? +1 for the hyperlink - it was very helpful. – w0051977 Jan 25 '18 at 8:29
  • Can you answer the question in my comment above before I mark your answer? Thanks. – w0051977 Jan 25 '18 at 14:02
0

IOfferValueCalculator does not sit in the innermost layer of the Onion

It probably should - or at least be treated as such. If I understand correctly, the code is taken from a presentation where the speaker refactors a service-oriented anemic design into a rich domain model. There might still be traces of the old design in the "after" code. That there remains a separate Services layer post-refactoring might not be intentional.

should I be mocking IOfferValueCalculator (a Domain Service)?

  • Some rare domain objects may be injected with dependencies that talk to the outside world, i.e. cross architectural boundaries. This should probably be avoided, but if it cannot, this is when mocks are needed. In any case, you should mock the dependency, not the domain object.

  • The rest of the time, even if in a Domain-ish layer next to Domain, I agree with the articles you cited that isolated domain tests have no need for mocks.

  • Thanks. Can you confirm that when you talk about "isolated domain tests have no need for mocks" then this includes Domain Services as no-one specifically specifies this - they specify Entities; Values and Domain Events. – w0051977 Jan 25 '18 at 9:09
  • Yes. The logic behind it is that the domain is a central self-sufficient bubble and domain services are no exception. – guillaume31 Jan 25 '18 at 14:16
  • Also note that domain services are not the only kind of Service in DDD - it is more legitimate to use mocks in tests for the other kinds. – guillaume31 Jan 25 '18 at 14:17
  • Thanks. I have a namespace called: Company.Technology.Feature.AggregateRoot. The Domain Service is used by this aggregate root only Would the Domain Service be: Company.Technology.Feature.AggregateRoot.DomainService or would you put it in a different namespace to the aggregate root? If they are all one unit then I believe they should be contained in the same namespace. – w0051977 Jan 25 '18 at 14:24
  • Could you clarify what you mean by: "there may be traces of the old design in the after code"? – w0051977 Jan 25 '18 at 23:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.