Implementing a use case (V) — Given-When-Then Testing Style
This post is part of Implementing a Use Case, a series of posts where I share my learnings on designing, architecting and implementing use cases. I suggest reading the previous posts where I already explained some concepts you might find here.
You can either take a look at the code of the application example or the different tags which show the state of the application at the moment of the post.
NOTE: I’ve moved the classes placed under the Common
folder to different libraries that you can find and use in this new php-soa repository.
In previous posts, I introduced the concept of a Command to represent the intent to do some action, and the Domain Event to represent the resulted fact of executing the Command. We can now model our use cases by using these two building blocks, making our code readable and easy to maintain.
In this post, you will see how we can improve the way we test our use cases by doing a couple of changes to our current approach. We will refactor our tests from:
To a more representative way by using the well known Given-When-Then approach that product people, such as the Product Owner of the team, can use to describe the acceptance criteria of a given user story:
In addition to improving our tests readability, we will improve also our use cases by removing the persistence logic from our CommandHandlers.
Removing Persistence Logic From Use Cases
Let’s remember how a use case looked like so far:
Notice that the first step is to retrieve the state of the PullRequest model at the beginning of the use case, then apply some business rules to satisfy invariants, and finally persist the resulted events. We can easily move these two steps to our CommandBus by placing this logic into Middlewares. Let’s see first the new use case approach:
The only two changes are: we receive now the PullRequest data as a parameter, and the Domain Events are returned. But, what if our use case does not expect a PullRequest to be passed such as when creating a new one? Easy, we can mark the signature of the method in this way:
This way we have marked our use case as if it does not expect (null
) the second argument.
On the other hand, since we have moved the persistence logic to our CommandBus, we need to add/modify some Middlewares:
Now, the CommandHandlerSelectorMiddleware
checks whether the use case expects a model to be passed (remember, the null
convention in the second argument):
I have also added the PersistEventStreamMiddleware
to the bus, which receives the result of executing the use case and persist the events:
The Result
I see many improvements in our use cases design:
- We don’t have to deal with persistence logic anymore so that we can focus all our attention on the behavior (invariants) when implementing our use cases. We assume that the persistence logic is always executed as it is part of our bus.
- Use cases return the Domain Events which makes them easier to be tested as we don’t need to fake the Repository to check if the
save
method was called. - We have removed a common side effect from our use cases which is calling the database.
- Of course, the design of our tests following the Given-When-Then approach.
NOTE: if you want to see the implementation details of the CommandHandlerTestCase
class that manages the Given-When-Then scenario you can take a look at the library repository.
I hope you liked the approach. In the next post, I will show how we can improve the way we are persisting both events and the state of our models by introducing the concept of the Projection.