How can I test the Account class, or event the Money class?
This question has a couple of different flavors of answer.
The most straight forward answer is that we are using the domain model manage change to a data model. That Money.value
came from somewhere, and the motivation for invoking Account.withdraw
is to change that specific value at its source.
Given: an initial state for the source
When: the domain model is directed to perform a behavior
Then: the change to the source satisfies the specification
For state of the resource, think DTO; a representation of that state designed to cross process boundaries.
The same idea expressed another way: there must be a way to inject data into the domain mail, and extract it back out. There's something that understands how to take primitive inputs and transform them into Value Objects; and like wise there must be something that understands how to take Value Objects and convert them back into primitive outputs.
If you are familiar with TDD, you see this approach taken when designing a new class -- consider the bowling game kata. The test models rolls as int inputs, and models the score as an int output. When we start making those concepts explicit in the model, we write code to transform the primitives to the explicit concept, and vice versa.
A slightly different variation; what we are typically doing with a domain model is ensuring that changes to a book of record satisfy some business invariant. So we think in terms of passing the "book of record" to the model, let the model make the changes, and then inspect the book of record to verify the changes.
// Given
Model m = Model.from(bookOfRecord);
Command c = Command.from(message);
// When
{
m.runCommand(c);
}
// Then
assert specification.isSatisfiedBy(bookOfRecord);
A different answer came from Greg Young, when he introduced the probability kata "test the model in the calculus of itself".
For example, the fact that, in your example, the domain model uses a floating point value to track the current state of Money is an implementation detail. It's one that is likely to change as your model gets more sophisticated about rounding rules.
That kind of a change shouldn't break all of your tests.
The "state" of the model should be a black box; what the tests should be focused on is not that the model ends up in the expected "state", but instead that the model produces the right responses to queries.
Some of those queries may be as simple as "is the model internally consistent?"