1

I'm a student and I'm learning about domain driven design. From my reading, an important part is removing the getters and setters from a class (thus keeping the implementation of that class private). My question is how can you do unit tests for this class.

Consider the following example:

public class Account {
    private Money money;

    public void withdraw(float money){
        this.money.subtract(money);
    }
}

public class Money {
    private float value;
    private Currency currency;

    public void subtract(float money) {
        this.value -= money;  
    }
}

How can I test the Account class, or event the Money class? In case of an error I can throw an exception and configure the test so that it expects that exception. What about the happy path? How can I check if the value after the method is called is the correct value?

  • 3
    If you return the remaining balance (i.e. `value`) from the `subtract` method, you can assert against that. Having a getter just to see if `value` is correct is ill-advised; you're breaking encapsulation at that point. – Robert Harvey Sep 08 '17 at 20:41
  • I used this Account example because it's simple and I couldn't think of another one. It's true in this case I can return the remaining balance, but I was wandering in the general case, where maybe you don't have this kind of possibility. What can you do in that case? I can give another example. Let's say you test a Repository's retrieve method. How can you check if the entity retrieved is the one you requested? – Georgian Vladutu Sep 08 '17 at 20:46
  • Same way, by asserting against the result returned from the method. If you need to be more nuanced than that, look into *Test Doubles* like Mocks and Stubs. But the general idea is to write code that's testable, so that you can avoid elaborate testing schemes. – Robert Harvey Sep 08 '17 at 20:49
  • "From my reading, an important part is removing the getters and setters from a class (thus keeping the implementation of that class private)." Applying something you read without first understanding WHY it is recommended is extremely bad idea. I would recommend first studying this aspect first before asking us. – Euphoric Sep 08 '17 at 21:02
  • 1
    Possible duplicate of [Are Get-Set methods a violation of Encapsulation?](https://softwareengineering.stackexchange.com/questions/120828/are-get-set-methods-a-violation-of-encapsulation) – Euphoric Sep 08 '17 at 21:07
  • 6
    "What about the happy path?" -- yes, what about it? what would be a happy path for the code snippets here, how these could be used? I can't see how - this code just doesn't make sense. Think of it, you test what you can use - if there's no use there's nothing to test – gnat Sep 08 '17 at 21:21
  • 1
    @Euphoric: Eh, not really. That's more of a "gnat swipe" duplicate, the usual (ill-advised) practice we have here of marking as a duplicate of a question that addresses some relevant, but peripheral point in an oblong way, but doesn't really address the original question. – Robert Harvey Sep 08 '17 at 21:22
  • 1
    @RobertHarvey While it is not exactly same question. It does invalidate core assumption in this question. Removing all getters and setters in name of "protecting encapsulation" is irrational idea. Problem in this question stems from adhering to that irrational idea. It is similar to XY Problem. – Euphoric Sep 08 '17 at 21:25

1 Answers1

1

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?"

VoiceOfUnreason
  • 32,131
  • 2
  • 42
  • 79