Creating valid test data for a large constrained object is cumbersome.
test the entire group with a social unit test requires larger setup and larger input ⇒ object mother pattern, i.e. a shared class creating valid test objects
same test data factory used in different verticals
introduces coupling, hard to change
⇒ break object mother per vertical
break domain entities in separated bounded contexts
Unit tests speak your domain model. Unit tests are first class citizen of your project. Respect them.
Unit testing loves:
precise signatures
smaller data structures
agnostic domain
Unit testing puts design pressure on highly complex logic.
Fixture creep
Complex methods in the same class use different sets of dependencies, e.g.:
class Wide { A a; B b; complexA() { .. a.fa(); .. } complexB() { .. b.fb(); .. }}
Then you might have to mock A and B.
Mitigation: split unrelated complexity: horizontal split class.
In this example, we could have a WideA that contains only A and WideB that contains only B.
Mock roles, not objects
⇒contract testing
Before mocking a dependency, clarify its responsibility.
Changing an API you mocked is painful.
Functional programming
Unit testing promotes functional programming:
pure functions
has no side-effects
same inputs ⇒ same outputs
just compute value
no network or file
no changes to data
no time/random
immutable objects
If you have a very complex logic using many dependencies (e.g. computePrice, applyDiscounts), move the complex part in another class, i.e. reduce coupling of complex logic.
a = repo1.findById(..);b = repo2.findById(..);c = api.call(..);doComplex(a,b,c);repo3.save(d);mq.send(d.id);
move complex logic in pure function:
D pure(a,b,c) { return d;}
It will be easier to test with less mocks.
Imperative shell / functional core segregation
Move complex logic in the functional core.
All the imperative shell shall be used for dependencies, state mutation, API calls, DB, files, …
Temporal coupling
You use mutable objects, so by swapping two lines can still cause bugs in production.
This might lead to paranoid testing, i.e. verifying method call order.
Instead, use immutable objects.
Design hints from tests
collapse middle-mand vs “What am I testing here?” syndrom