Domain-Driven Design: Striving for clarity in tests with factories

Bruno Vegreville
inato
Published in
7 min readSep 10, 2019
Photo by Osman Rana on Unsplash

The ultimate goal to have in mind when writing tests is that someone reading your test should immediately understand what is asserted.

This article will address some issues frequently encountered when testing large or complex entities or aggregates, especially in Domain-Driven Design setting.

We will use Jest as the testing library for the rest of the article but the key takeaways do not depend on this library.

The final version of the source-code presented in this article can be found here : https://github.com/inato/tests-clarity-in-DDD

Simplifying object creation with object factories

You should always be able to create an object of your domain without specifying any arguments

Let’s say that a part of your domain is modeling how a hospital works and operates. A basic first draft could look like that :

You want to write a test to assert that the function hireSomeone is effectively raising the staff size by 1.
You first attempt could look like that :

This test is totally valid in terms of what is expected but misses one crucial point when it comes to readability : only what is relevant to the test’s expectation should be made visible in the test.

What is annoying in this test is that if someone reads this test, the reader would be totally right to ask the question :

But wait, is this recruitment strategy specific to France’s hospitals ?

We could imagine a domain where hospitals need first to check if they actually need a new doctor before validating the new hire.
The fact that we have explicitly created a french hospital introduces noise in the test and makes it hard to find what is relevant here.

For this test, only the initial and final staff sizes are relevant.

Reducing noise in tests with object factories

We will introduce a hospitalFactory, a helper function able to create a Hospital with partial arguments.

For this simple domain object, writing a factory is very straightforward :

  1. All factory’s arguments are optional and provided with defaults
  2. The factory always return the related domain object

Let’s rewrite the previous test with our new factory :

The expectation of this test has not changed but the result is way clearer :

By reading this test we know that the only thing relevant to the assumption enforced is the staff size of the hospital

Therefore, when following DDD principles in your codebase, a good rule is to always include a factory with a domain object.

Object factories : Extensibility vs Simplicity of use

A few days later, you want to refine your domain around Hospital and want to provide enhanced vision on the staff working inside a hospital. Your refined class looks like this :

We have introduce a new Entity in our domain, the Doctor.

What is important to understand for the next sections is:

  • Each doctor has an unique id that identifies it among other doctors.
  • A hospital has a staff which is a list of doctors.
  • One doctor among the staff is special, and is also the director of the hospital.
  • A hospital should always have his director included in the staff, otherwise the creation throws. Please note that throwing in the constructor is nor recommended nor aligned with DDD principles but is only used here for simplicity. For more expressive error-handling, you can check the following article :

Re-using learnings from section 1 : doctorFactory

Since we have already understood the benefits of object factories, we will refactor hospitalFactory and introduce doctorFactory :

The doctorFactory is fairly simple and follows the same principles developed in section 1. hospitalFactory has been extended to support the new attributes and try to build a valid hospital by default (by putting the director in the staff if the staff is undefined)

You can see how factories will gain value over time : doctorFactory helps testing the business logic around Doctors but also helps the creation of Hospital objects.

Using the factories to test business logic around Hospital

We want to test that :

  • Hiring a doctor raises the staff size of the hospital by 1
  • Hiring a doctor already employed in the hospital does not raise the staff size by 1

Let’s try to test the method hire of the aggregate Hospital using our two factories :

The code is pretty clean in the sense that we do not mess with attributes not relevant to the test (e.g. firstName, country...), but we end up with another issue : because of the business logic around the director, every time we specify the staff when creating an hospital, we need to make sure that the director is included in the staff, otherwise the creation will throw.

This is additional noise we would like to remove, as these tests have nothing to do with directors.

As our domain objects become more and more complex, creating them with only few arguments becomes a challenge

Specialized factories helps reduce the burden of creation

With the same objectives in mind (e.g. creation of object should be simple and arguments should be optional) we will create a specialized factory to be able to create a hospital with a specific doctor in the staff without messing with the director concept.

The main characteristics of this specialized factory are :

  • This factory handles for us the burden to make sure that the director is included in the staff. You can even use this factory with doctor: null to only include the director in the initial staff.
  • This factory falls back to the generic hospitalFactory to provide the other defaults

The job of this factory is “Give me a doctor and I will return a valid hospital with this doctor in the staff”.
Using this specialized factory will turn our tests to :

You can see that nowhere in these tests is the concept of director present, and that all irrelevant variables have been removed.

The combination of general object factories and specialized factories is very powerful and is increasingly beneficial to your codebase as your objects become more and more nested.

To summarize, object factories help us solve two issues:

  1. Only specifying what is relevant to the context of the test
  2. Making it easy to build an object from our domain, even if the underlying logic embedded in this object is complex

Reducing the fixed cost of testing use-cases with factories

Now that building objects from our domain have become easy thanks to our factories, we are interested in testing an use-case from our domain, namely replaceSomeoneInHospital which replaces one doctor by another one in the staff of the hospital (if the first doctor is sick for instance).

This use-case takes as arguments :

  • initialDoctor: Doctor to replace
  • newDoctor: Doctor to put instead of the replaced one
  • hospitalName: string name of the hospital that is concerned by the replacement (the related Hospital object will be found by the use-case using the hospitalRepository)
  • hospitalRepository: HospitalRepository which is in charge of finding or storing Hospital objects (using databases, in memory or whatever)

To overly simplify our point, we will design our use-case to return a Hospital object, equal to the updated hospital if the replacement was done successfully and null if the replacement could not be done.

We want to test one business rule for :

  • The replacement should not increase or decrease the staff size of the hospital

This test would naively look like this:

This is not so bad, as we don’t provide many variables not relevant to the test, but we have an issue that will become increasingly annoying as we test new assumptions for this use-case : We had to set-up some objects just to be able to call the use-case.

These objects, like hospitalRepository or the fact that the hospital needs to be stored in the repository before calling the use-case, are not relevant to this test and hurt readability.

The fixed cost of setting up objects for the use-case will add noise in each test of this use-case

To reduce the burden around calling the use-case, we will refactor this set-up in a function that will do most of it for us and return a simplified (e.g. curried) version of our use-case, that will only take relevant variables as inputs.

If you look at the size of the test inside the it() brackets with and without the use case setup helper, you can understand why the benefits are very substantial as we test more rules for our use-case.

The main benefits however are related to the readability of this test :

  • The set-up helper takes care of creating the hospital with the initial doctor and storing the hospital.
  • The helper returns a curried version of the use-case, where we only need to provide relevant inputs, here the doctor that will replace the initial doctor.

The final version of the source-code presented in this article can be found here : https://github.com/inato/tests-clarity-in-DDD

Drug discovery is a challenging, intellectually complex, and rewarding endeavour: we help develop effective and safe cures to diseases affecting millions of people. If you’re looking to have a massive impact, join us! https://angel.co/inato/jobs/

--

--