Business Rules & (Unit) Testing

Domain driven design is intended for complex business domains, and so testing is obviously important. In this part of the tutorial we’ll cover unit testing, later on we’ll look at integration testing.

Defaults, and ClockService

By way of motivation, let’s consider a small enhancement we could make to the app. For example, it would improve the usability if the app automatically suggesting a time for a new Visit that was in the future (say tomorrow, at 9am):

Pet bookVisit prompt with default

Actually, the design of this app is probably all wrong. Rather than choosing some arbitrary time in the future for a visit, more likely there would be a number of pre-defined "appointment slots".

One of the strengths of the framework is to allow the development team to uncover these missing concepts as quickly as possible. It also means we are able to "let go" of bad ideas (we become less emotionally attached to them).

Solution

git checkout tags/190-defaults-and-clockservice
mvn clean package jetty:run

Exercise

The default is specified using a supporting method:

public LocalDateTime default0BookVisit() {
    return clockService.now()
                .plusDays(1)
                .toDateTimeAtStartOfDay()
                .toLocalDateTime()
                .plusHours(9);
}

@javax.jdo.annotations.NotPersistent
@javax.inject.Inject
ClockService clockService;

The name of this supporting method is "default" + "paramNum" + "actionName".

The (framework provided) ClockService provides the current time. Why do this (rather than simply instantiating LocalDateTime?) We’ll see why in the next session.

Unit tests

Let’s now write a unit test to safeguard the logic to calculate the default time for the visit. Now we see the reason why we use a domain service to obtain the time; it allows us to "mock the clock".

Solution

git checkout tags/200-unit-tests
mvn clean package jetty:run

Exercise

The Apache Isis framework provides some extensions to JMock to make writing unit tests really simple.

public class Pet_bookVisit_Test {

    @Rule
    public JUnitRuleMockery2 context =                                  (1)
        JUnitRuleMockery2.createFor(JUnitRuleMockery2.Mode.INTERFACES_AND_CLASSES);

    @Mock                                                               (2)
    ClockService mockClockService;

    @Test
    public void default0BookVisit() {

        // given
        Pet pet = new Pet(null, null, null);
        pet.clockService = mockClockService;                            (3)

        // expecting
        context.checking(new Expectations() {{                          (4)
            allowing(mockClockService).now();
            // 3-Mar-2018, 14:10
            will(returnValue(new LocalDate(2018,3,3)));
        }});

        // when
        LocalDateTime actual = pet.default0BookVisit();

        // then
        assertThat(actual).isEqualTo(new LocalDateTime(2018,3,4,9,0));  (5)
    }
}
1 to set up expectations on mocks. All configured expectations are also automatically verified.
2 automatically instantiated by JMock
3 inject the mock clock into the domain object
4 set up expectation on the mock clock
5 use AssertJ to assert the expected value

Validation

It doesn’t really make sense to book a visit in the past. Let’s fix that with some validation: s image::./Pet-bookVisit-prompt-with-validate.png[width="800px",link="_images/Pet-bookVisit-prompt-with-validate.png"]

Solution

git checkout tags/210-validation
mvn clean package jetty:run

Exercise

The validation rule is implemented using a supporting method (though a specification could also have been used):

public String validate0BookVisit(final LocalDateTime proposed) {
    return proposed.isBefore(clockService.nowAsLocalDateTime())
            ? "Cannot enter date in the past"
            : null;
}

The name of this supporting method is "validate" + "paramNum" + "actionName".