Montag, 23. Mai 2011

Simplifying JMock Expectations

I like to use JMock for mocking depending objects when test-driving my code. It allows a very readable description of mock object behaviour. Readability is my first priority when writing tests so that they are easy to understand later, too, and can be used as a valuable documentation of the intended behaviour. In order to lift the readability of JMock expectations I used a generalized implementation of the "Object Mother"-Pattern.

The "Object Mother"-Pattern

The "Object Mother"-Pattern is known to express in a succinct form what makes an object special.
Let's assume we have a domain object Person with the two attributes name and surname.
class Person {
  String _name, _surname;
  
  public Person(String name, String surname) {
    _name = name;
    _surname = surname;
  }
}

When we want to assure that a Person object with an empty surname is rejected by a PersonRepository when trying to save it, the simple solution would be the following:
@ExpectedException(ConstraintViolationException.class)
@Test public void shouldRejectPersonWithEmptySurname {
  Person personWithEmptySurname = new Person("Name", "");

  personRepository.save(personWithEmptySurname);
}

Though this is quite good (when using intent-revealing names), it becomes tedious when you want to test the behaviour in all possible attribute combinations. That's why the "Object Mother"-Pattern is handy. A possible ObjectMother for Person is:

class PersonMother {
  Person _child = new Person("Name", "Surname");

  PersonMother withEmptyName() {
    _child._name = "";
    return this;
  }
  
  PersonMother withEmptySurname() {
    _child._surname = "";
    return this;
  }
  
  Person build() {
    return _child;
  }
}

Using the PersonMother and a simple factory method we can simplify the test code:

@ExpectedException(ConstraintViolationException.class)
@Test public void shouldRejectPersonWithEmptySurname {
  personRepository.save(aPerson().withEmptySurname().build());
}

PersonMother aPerson() {
  return new PersonMother();
}

And the PersonMother simplifies the combination of attributes, too:

@ExpectedException(ConstraintViolationException.class)
@Test public void shouldRejectPersonWithEmptyNameAndSurname {
  personRepository.save(
      aPerson()
        .withEmptyName()
        .withEmptySurname().build());
}

Using the Object Mother for JMock expectations

After this excursus in the world of patterns, I am approaching my point, finally. Assuming that we want to test the behaviour of a service that depends on a PersonRepository to access persisted persons. In order to test the service in isolation, I mock the PersonRepository. I want to test the behaviour of the service depending on the contents of a returned Person. So we want to return different persons from the mocked repository. With "plain" JMock this results in the following setup code for our mock:

PersonRepository mockPersonRepository = context.mock(PersonRepository.class);
context.checking(new Expectations() {{
  oneOf(mockPersonRepository).get(1); returnValue(aPerson().withoutSurname().build());
}});
[...]

Not bad, actually. Very expressive and readable. But since I am quite a perfectionist, I am bothered by the required call to build(). So, let's see if we can simplify this further.

At first, I extract a generic interface from PersonMother:
interface ObjectMother<T> {
  T build();
}

class PersonMother implements ObjectMother<Person> {
  [...]
}

Afterwards, I create a new method to use in JMock expectations:

static Action returns(ObjectMother<?> objectMother) {
  return new ReturnValueAction(objectMother.build());
}

With these two ingredients I am able to reduce the mock setup code as I wanted:

PersonRepository mockPersonRepository = context.mock(PersonRepository.class);
context.checking(new Expectations() {{
  oneOf(mockPersonRepository).get(1); returns(aPerson().withoutSurname());
}});
[...]

Keine Kommentare:

Kommentar veröffentlichen