Fluent Java APIs II: The MockBuilder Pattern

Context

  • In a TDD environment, the same coding standards should be enforced on Unit Test code as on application code. Often, however this isn’t the case! Remember: unit tests can contain technical debt too!
  • Mocking service calls with EasyMock involves repetitive code.
  • This repetitive code can be simplified, and rendered more fluent with the Builder pattern.

Benefits of a MockBuilder

  1.   Sharpened focus: unit tests becomes more focussed on the code under test , rather than code written to support or prepare the test.
  2. Cheaper to write more and more tests.

First Iteration: mocking a service call to the ClientService with EasyMock

Consider an example of a unit test where there is a call to the ClientService that we want to replace with a call to a Mock:

@Test
public void testLoadPresetFormWithClient() throws RemoteException {

    NewBusinessApplication nba = NewBusinessApplicationBuilder.create()
    .withPresetOwner()
    .build();

    nba.getOwner().setRegisteredAddress(null);
    nba.getOwner().setClientNumber("12345678");

    Client stubbedClient = new Client();
    stubbedClient.setAddress(new Address());
    stubbedClient.getAddress().setLine1("Client Address 1");
    stubbedClient.getAddress().setLine2("Client Address 2");
    stubbedClient.getAddress().setLine3("Client Address 3");
    stubbedClient.getAddress().setLine4("Client Address 4");
    stubbedClient.getAddress().setLine5("Client Address 5");

    ClientService mockClientService = EasyMock.createMock(ClientService.class);
    EasyMock.expect(mockClientService.findByClientNumber((String)EasyMock.anyObject())).andReturn(stubbedClient).anyTimes();
    EasyMock.replay(mockClientService);
    EasyMock.verify(mockClientService);

    ClientServiceDelegate.getInstance().setClientService(mockClientService);

    EmployerCorrespondenceDetailsForm form = service.loadPresetForm(nba);

    assertFormAndClientRegisteredAddressEquals(form,mockClientService.findByClientNumber("12345678"));

    assertFormAndApplicationBusinessAddressEquals(form,nba);
}

The vast majority of the code in this unit test is support code, written to mock the client service call, and ensure a stubbed client is used for the purposes of the test.

In fact , only the last three lines of this test method are actual test code, executing the method under test, and evaluating the results

Second Iteration: Create ClientServiceMockBuilder

All the semi-boilerplate EasyMock code related to mocking the client service is now moved to it’s own object, where the builder pattern is implemented:

public class ClientServiceMockBuilder {

    private ClientService mockClientService;

    public static ClientServiceMockBuilder create() {
        return new ClientServiceMockBuilder();
    }

    public ClientService build() {
        EasyMock.replay(mockClientService);
        EasyMock.verify(mockClientService);
        return mockClientService;
    }

    private ClientServiceMockBuilder() {
        mockClientService = EasyMock.createMock(ClientService.class);
    }

    public ClientServiceMockBuilder withFindByClientNumberStubbedClient(){
        try{
            EasyMock.expect(mockClientService.findByClientNumber((String)EasyMock.anyObject())).andReturn(createStubbedClient()).anyTimes();
        }   catch(RemoteException e){
            throw new RuntimeException(e);
        }
        return this;
    }

    private Client createStubbedClient(){
        Client client = new Client();
        client.setAddress(new Address());
        client.getAddress().setLine1("Client Address 1");
        client.getAddress().setLine2("Client Address 2");
        client.getAddress().setLine3("Client Address 3");
        client.getAddress().setLine4("Client Address 4");
        client.getAddress().setLine5("Client Address 5");
        return client;
    }

}

Revisiting the Unit Test, it is now a lot cleaner, and more readable. More to the point though, we are pointing the way to any unit test that needs to mock a call to the Client Service:

@Test
public void testLoadPresetForm_WithClient() throws RemoteException {

    NewBusinessApplication nba = NewBusinessApplicationBuilder.create()
     .withPresetOwner()
     .build();

    nba.getOwner().setRegisteredAddress(null);
    nba.getOwner().setClientNumber("12345678");

    ClientService mockClientService = ClientServiceMockBuilder.create()
     .withFindByClientNumberStubbedClient()
     .build();

    ClientServiceDelegate.getInstance().setClientService(mockClientService);

    EmployerCorrespondenceDetailsForm form = service.loadPresetForm(nba);

    assertFormAndClientRegisteredAddressEquals(form,mockClientService.findByClientNumber("12345678"));

    assertFormAndApplicationBusinessAddressEquals(form,nba);
}

Third Iteration: Abstract out common MockBuilder behaviour

If your unit test code involves the use of many different mocked objects, you’ll be building a lot of MockBuilder objects to correspond with these. You’ll also quickly notice repetition of certain code, which can be abstracted to an abstract parent class called MockBuilder, as below:

public abstract class MockBuilder<T extends Object> {

    protected T mockedObject;

    public T build() {
        EasyMock.replay(mockedObject);
        EasyMock.verify(mockedObject);
        return mockedObject;
    }

}

The updated ClientServiceMockBuilder now looks like this:

public class ClientServiceMockBuilder extends MockBuilder<ClientService> {

    public static ClientServiceMockBuilder create() {
        return new ClientServiceMockBuilder();
    }

    private ClientServiceMockBuilder() {
        mockedObject = EasyMock.createMock(ClientService.class);
    }

    public ClientServiceMockBuilder withFindByClientNumberStubbedClient(){
        try{
            EasyMock.expect(mockedObject.findByClientNumber((String)EasyMock.anyObject())).andReturn(createStubbedClient()).anyTimes();
        }   catch(RemoteException e){
            throw new RuntimeException(e);
        }
        return this;
    }

    private Client createStubbedClient(){
        Client client = new Client();
        client.setAddress(new Address());
        client.getAddress().setLine1("Client Address 1");
        client.getAddress().setLine2("Client Address 2");
        client.getAddress().setLine3("Client Address 3");
        client.getAddress().setLine4("Client Address 4");
        client.getAddress().setLine5("Client Address 5");
        return client;
    }

}

And we can extend the MockBuilder again as required. For example, here’s another implementation, a Spring MVC BindingResultMockBuilder

public class BindingResultMockBuilder extends MockBuilder<BindingResult> {

    public BindingResultMockBuilder() {
        mockedObject = EasyMock.createMock(BindingResult.class);
    }

    public static BindingResultMockBuilder create() {
        return new BindingResultMockBuilder();
    }

    public BindingResultMockBuilder withErrors(boolean hasErrors) {
        EasyMock.expect(mockedObject.hasErrors()).andReturn(hasErrors).anyTimes();
        return this;
    }

    public BindingResultMockBuilder withAddAllErrors() {
        mockedObject.addAllErrors((Errors) EasyMock.anyObject());
        EasyMock.expectLastCall().anyTimes();
        return this;
    }

    public BindingResultMockBuilder withGetAllFieldErrors() {
        EasyMock.expect(mockedObject.getFieldErrors()).andReturn(new ArrayList<FieldError>()).anyTimes();
        return this;
    }

    public BindingResultMockBuilder withRejectValue() {
        mockedObject.rejectValue((String) EasyMock.anyObject(), (String) EasyMock.anyObject());
        EasyMock.expectLastCall().anyTimes();
        return this;
    }
}

Results

  1. The MockBuilder can be extended to create a new subclass for anything you need to mock.
  2. Large amounts of boilerplate code can be refactored into a simpler invocation of a Builder.
  3. Cleaner Unit Tests = Better Unit Tests.

Leave a comment