tl;dr Since I embraced Domain-Driven Design, I didn't have to use any mock.
When in 2011, during Java Developers Day in Kraków, Greg Young stated, that he does not use mocks, I couldn't believe him. I mean, come on! How do you test your code that is dependent on other parts of the code? I was at the very begining of my journey with Domain-Driven Design back then...
Couple of days ago, I had to implement something in old part of the system. This part was developed like most systems were couple of years ago (and some are still) - with Anemic Entities and stateless, so called "Services". I didn't want to refactor this whole spaghetti around that place, so I decided to go with this anemic approach and "just be good enough".
Since I am a big fan of Test-Driven Development (where it
is valuable), I decided to write some test for this service that I was
about to touch. Suprisingly, there was a test class for this service,
and there were even some not that bad unit tests there. Woohoo, lucky
me!
I wrote first unit test, run - red of course - so I implemented some simple solution - green - I added another test - red - another simple solution - green - I refactored a little - green. Uncle Bob would be proud!
Then, the time has come to write some more complex case. I realized, that I need some additional service to be injected into mine. It was clear to me, that I needed to mock that service in test, so I wrote
I wrote first unit test, run - red of course - so I implemented some simple solution - green - I added another test - red - another simple solution - green - I refactored a little - green. Uncle Bob would be proud!
Then, the time has come to write some more complex case. I realized, that I need some additional service to be injected into mine. It was clear to me, that I needed to mock that service in test, so I wrote
@Mock private AdditionalService additionalService;
and it felt weird... :/
I realised, that I haven't use mocks for a long time. I just didn't have to.
Testing business logic
In Domain-Driven Design, you aim to model true invariants. Your goal is to find boundaries inside the model and isolate business logic from infrastructure. If you succeed, your business logic is decoupled and highly cohesive. All additional things, that model may require, should be injected from higher layer - Application Layer, which is responsible for orchestrating domain scenario, but has nothing to say about business logic.
I realised, that I haven't use mocks for a long time. I just didn't have to.
Testing business logic
In Domain-Driven Design, you aim to model true invariants. Your goal is to find boundaries inside the model and isolate business logic from infrastructure. If you succeed, your business logic is decoupled and highly cohesive. All additional things, that model may require, should be injected from higher layer - Application Layer, which is responsible for orchestrating domain scenario, but has nothing to say about business logic.
If you do proper modeling, you only want to unit test
business logic, which very striclty defines what is required at the
begining (Given), what action you perform (When), and what are the outcomes
(Then). It is very easy to instantiate small graph of objects (properly
designed Aggregate), and test it in isolation. You don't need to mock
anything. You just test the business logic.
Of course, you do want to test Application Layer (to check if everything is properly wired and so on), but then you do not unit test it. There are higer level tests, that should be executed against this layer - like acceptance tests. And then, you definitely do not want to mock anything. :)
Testing anemic models
On the other side, in classic, Anemic approach, you cannot achieve that. You just have "entities", which are in fact database records, and services, that heavily execute setters and getters on those "entities". Those services call other services, and those, invoke even more getters and setters. There are no clear boundaries, the coupling is high and cohesion is low.
As you probably experienced that, this is a situation, where you have large, unbounded graph of objects, without any encapsulation involved. You manually manipulate it in order to perform some business logic. If you would like to test some logic, that runs on top of some part of that graph, you need to either
Of course, you do want to test Application Layer (to check if everything is properly wired and so on), but then you do not unit test it. There are higer level tests, that should be executed against this layer - like acceptance tests. And then, you definitely do not want to mock anything. :)
Testing anemic models
On the other side, in classic, Anemic approach, you cannot achieve that. You just have "entities", which are in fact database records, and services, that heavily execute setters and getters on those "entities". Those services call other services, and those, invoke even more getters and setters. There are no clear boundaries, the coupling is high and cohesion is low.
As you probably experienced that, this is a situation, where you have large, unbounded graph of objects, without any encapsulation involved. You manually manipulate it in order to perform some business logic. If you would like to test some logic, that runs on top of some part of that graph, you need to either
- instantiate whole graph, which may be hard and expensive, or
- instantiate the part, you need at the moment and mock the surrounding parts
This weird @Mock feeling
We, software developers, are very good at solving problems. On the other hand, we are not so good at finding root causes of those problems. When our graphs of objects grew, we didn't stop to ask why. The problem, that occured was simple - we couldn't easily test them. So... We have found a workaround - mocks! Easy workaround... Useful workaround... But still... A workaround...
Domain-Driven Design helped me to focus on the question "why". Whenever I do proper modeling, I end up with design, that does not require any mocks in order to be tested. That's why I felt weird, when I had to use @Mock.
We, software developers, are very good at solving problems. On the other hand, we are not so good at finding root causes of those problems. When our graphs of objects grew, we didn't stop to ask why. The problem, that occured was simple - we couldn't easily test them. So... We have found a workaround - mocks! Easy workaround... Useful workaround... But still... A workaround...
Domain-Driven Design helped me to focus on the question "why". Whenever I do proper modeling, I end up with design, that does not require any mocks in order to be tested. That's why I felt weird, when I had to use @Mock.
PS.
I still see the advantage, which mocks give us in some cases - like dealing with legacy code, or some marginal, weird corner cases - but I may certainly say, that I don't use them anymore, when I do DDD. I don't need to.
I still see the advantage, which mocks give us in some cases - like dealing with legacy code, or some marginal, weird corner cases - but I may certainly say, that I don't use them anymore, when I do DDD. I don't need to.
Hi, Piotr,
ReplyDeleteGreat post especially in this "TDD is dead" [Martin Fowler etc] time and fighting with mocking in TDD.
What's your approach to "not mock" ?
You mentioned that all additional things should be injected to models. Do you inject concrete implementations while testing? Do you use IoC containers while testing or just inject dependencies manually (in constructor) ?
First of all - sorry for the late response.
DeleteAnd now the answer:
It all depends. :)
I tend to inject all additional dependencies as closures to my methods - no IoC magic - in the model everything has to be as explicit as it can be.
If there are too many of them in one method, that is a smell, that the design may be broken.
Sometimes I need some Value to be injected (calculated in different Aggregate, or in other system hidden behind Anti-Corruption Layer) - then in test I can just create that Value manually.
Sometimes I need some Domain Service, or Function - then in the test I can just pass the concrete implementation from my domain, or create dumb-stupid stub implementing the interface of that Domain Service/Function.
Does this answer your question?
But to be honest, I can not remember the last time I created any stub... ;)
DeleteThanks Piotr, this is what I wanted. If you don't have an inspiration for further post, this could be one :]
Delete