Test Doubles: creating a Test Stub

In this series of posts about Test Doubles we’ve looked at the manual and tool-aided creation of a dummy object. Now it’s time to discuss the creation of a stub. The stub is defined as follows.

A Test Stub is an object that replaces a real component on which the SUT depends so that the test can control the indirect inputs of the SUT. It allows the test to force the SUT down paths it might not otherwise exercise.

Reasons for using a stub may vary. Some (not all) valid motivations for stubbing are that the real component:

  1. talks to the database;
  2. communicates across the network;
  3. touches the file system;
  4. performs too slowly to be part of a unit test;
  5. costs money to use;
  6. is not implemented (yet).

The first three reasons are based on Micheal Feathers’ list of characteristics that make a test no unit test.  A slow unit test – reason four – does not live up to “Fast” expressed in the mnemonic FIRST. Slow is not specific, but a way to decide that a test is slow is to look at it proportionately. How much slower is it than the average runtime of other simpler unit tests?
But even if we choose not to adhere to the previously mentioned principles, we are still left with reason five and six. Imagine a paid service such as a damage history lookup service for car insurance. The decision to stub that service is a no-brainer.
When there is no implementation (yet), we have no choice but to make a stub. Such a situation can occur when we apply a test first approach.
We’ll look at creating a stub for the sixth reason: no implementation yet. We’ll discuss the following:

  • Hard-coded Stub
  • Configurable Stub
  • Responder
  • Saboteur

If you want, you can clone / download the examples used in this post on github.

The SUT and its collaborators
We want to write two unit tests for the getAddress method in AddressService. A unit test that covers the path in which formatter.formatAddressNotFound() is returned and a test in that covers the path in which formatter.formatAddress(address) is returned.

public class AddressService {
  
  private final AddressRepository repository;
  private final AddressFormatter formatter;

  public AddressService(AddressRepository repository, AddressFormatter formatter) {
    this.repository = repository;
    this.formatter = formatter;
  }
  
  public String getAddress(String postCode, int houseNumber, String suffix) {
    final Address address = repository.getAddress(postCode, houseNumber, suffix);
    if (address == null) {
      return formatter.formatAddressNotFound();
    }
    return formatter.formatAddress(address);
  }

}

The interface AddressFormatter has two implementations HtmlFormatter and JsonFormatter which are already covered by unit tests. The interface AddressRepository is not implemented and looks like this.

public interface AddressRepository {
  
  /**
   * @param postCode
   * @param houseNumber
   * @param suffix
   * @return address with or without suffix, {@code null} when no address can be retrieved
   */
  Address getAddress(String postCode, int houseNumber, String suffix);
  
}

For our unit test we’ll have to make a stub the getAddress method in AddressRepository. The AddressFormatter does not necessarily need to be stubbed. But it does make sense to stub it, because we want the tests of getAddress in AddressService to fail for one reason and that reason is its own incorrect implementation. If we use a real AddressFormatter such as the JsonFormatter, we introduce a potential false negative. The unit test could fail because of an incorrect implementation of JsonFormatter.

The Hard-coded Stub
One way make a real AddressRepository is to create a class with a hard-coded response.

public class HardCodedStubAddressRepository implements AddressRepository {

  @Override
  public Address getAddress(String postCode, int houseNumber, String suffix) {
    City city = new City("Paradise City", new Province("FooBar", "FB"));
    Address validAddress = new Address("Test Street", 12, "c", city, "5555 TT");
    return validAddress;
  }

}

The same can be done for the AddressFormatter.

public class HardCodedStubAddressFormatter implements AddressFormatter {

  @Override
  public String formatAddress(Address address) {
    return "Address";
  }

  @Override
  public String formatAddressNotFound() {
    return "Address not found";
  }

}

These two hard-coded stubs function as a Responder. A Responder is a stub that is used to inject valid indirect inputs into the SUT. So now we are ready to write our first test.

  @Test
  public void should_find_an_address_with_two_hardcoded_stubs() {
    // setup the hard-coded stubs 
    AddressRepository stubRepository = new HardCodedStubAddressRepository();
    AddressFormatter stubFormatter = new HardCodedStubAddressFormatter();

    // instantiate the SUT with the hard-coded stubs 
    AddressService service = new AddressService(stubRepository, stubFormatter);
    // verify SUT
    assertEquals("Address", service.getAddress("5555 TT", 12, "c"));
  }

This works fine for the Responder. However, we still need a Saboteur. A Saboteur is a stub that is used to inject invalid indirect inputs into the SUT. That invalid input is null in this case. We can introduce an anonymous inner class.

  // anonymous inner class that acts as a saboteur
  AddressRepository saboteurStubRepository = new AddressRepository() {
    
    @Override
    public Address getAddress(String postCode, int houseNumber, String suffix) {
      Address invalidAddress = null;
      return invalidAddress;
    }
    
  };

Our second test looks like this.

  @Test
  public void should_find_no_address_with_two_hardcoded_stubs() {
    AddressFormatter stubFormatter = new HardCodedStubAddressFormatter();
    // instantiate the SUT with the hard-coded anonymous inner class stub
    AddressService service = new AddressService(saboteurStubRepository, stubFormatter);
    // verify SUT
    assertEquals("Address not found", service.getAddress("TT5555", 112, "^"));
  }

The downside of a hard-coded stub is evident. Important setup details are partially outside the test method. Consequently, the test is hard to understand without a look at the separately maintained stubs.

The Configurable Stub
A configurable stub solves that problem. We simply add setters to our stub classes to make our tests control the responses.

public class ConfigurableStubAddressRepository implements AddressRepository {
  
  private Address address;

  public void setAddress(Address address) {
    this.address = address;
  }

  @Override
  public Address getAddress(String postCode, int houseNumber, String suffix) {
    return address;
  }

}

public class ConfigurableStubAddressFormatter implements AddressFormatter {
  
  private String formattedAddress;
  private String formattedAddressNotFound;

  public void setFormattedAddress(String formattedAddress) {
    this.formattedAddress = formattedAddress;
  }
  
  public void setFormattedAddressNotFound(String formattedAddressNotFound) {
    this.formattedAddressNotFound = formattedAddressNotFound;
  }
  
  @Override
  public String formatAddress(Address address) {
    return formattedAddress;
  }

  @Override
  public String formatAddressNotFound() {
    return formattedAddressNotFound;
  }

}

The tests with configurable stubs are more self-contained and flexible.

public class ConfigurableAddressServiceTest {
  
  private ConfigurableStubAddressRepository stubRepository;
  private ConfigurableStubAddressFormatter stubFormatter;
  
  @Before
  public void setUp() {
    stubRepository = new ConfigurableStubAddressRepository();
    stubFormatter = new ConfigurableStubAddressFormatter();
  }

  @Test
  public void should_find_an_address_with_two_configurable_stubs() {
    City city = new City("Paradise City", new Province("FooBar", "FB"));
    Address validAddress = new Address("Test Street", 12, "c", city, "5555 TT");
    
    // configure the stubRepository as responder
    stubRepository.setAddress(validAddress);
    
    // configure the stubFormatter as responder
    stubFormatter.setFormattedAddress("Address");

    // instantiate the SUT with the configurable stubs 
    AddressService service = new AddressService(stubRepository, stubFormatter);
    // verify SUT
    assertEquals("Address", service.getAddress("5555 TT", 12, "c"));
  }

  @Test
  public void should_find_no_address_with_two_configurable_stubs() {
    Address invalidAddress = null;
    
    // configure the stubRepository as saboteur
    stubRepository.setAddress(invalidAddress);
    
    // configure the stubFormatter as responder
    stubFormatter.setFormattedAddressNotFound("Address not found");
    
    // instantiate the SUT with the configurable stubs 
    AddressService service = new AddressService(stubRepository, stubFormatter);
    // verify SUT
    assertEquals("Address not found", service.getAddress("TT5555", 112, "^"));
  }

}

This approach is better than the hard-coded stub. But is there an even better way? Rhetorically speaking, yes.

The Configurable stub with Mockito
We don’t need make our own stubs if we use Mockito.

public class AddressServiceTest {
  
  // the two stubs we want to use
  @Mock AddressFormatter stubFormatter;
  @Mock AddressRepository stubRepository;

  @Before
  public void setUp() {
    // use new stubs for each @Test
    MockitoAnnotations.initMocks(this);
  }
  
  @Test
  public void should_find_an_address_with_two_mockito_stubs() {
    City city = new City("Paradise City", new Province("FooBar", "FB"));
    Address validAddress = new Address("Test Street", 12, "c", city, "5555 TT");

    // configure the stubRepository as responder
    when(stubRepository.getAddress("5555 TT", 12, "c"))
      .thenReturn(validAddress);
    // configure the stubFormatter as responder
    when(stubFormatter.formatAddress(validAddress))
      .thenReturn("Address");
    
    AddressService service = new AddressService(stubRepository, stubFormatter);
    // verify SUT
    assertEquals("Address", service.getAddress("5555 TT", 12, "c"));
  } 

  @Test
  public void should_find_no_address_with_two_mockito_stubs() {
    Address invalidAddress = null;
    
    // configure the stubRepository as saboteur
    when(stubRepository.getAddress("TT5555", -12, "^"))
      .thenReturn(invalidAddress);
    // configure the stubFormatter as responder
    when(stubFormatter.formatAddressNotFound())
      .thenReturn("Address not found");
    
    AddressService service = new AddressService(stubRepository, stubFormatter);
    // verify SUT
    assertEquals("Address not found", service.getAddress("TT5555", 112, "^"));
  }

}

With @Mock we declare our stubs and in the setUp we instantiate them with initMocks. Configuring the stubs is self-explanatory. With when we tell the stub when to return something and with thenReturn we tell it what to return.

The Stub in context
Using a stub can be very helpful for isolating our (unit) tests and helping them give specific and otherwise hard to simulate feedback. However, using only stubs can become disadvantageous if we never use ‘the real thing’. Units should also be tested for integration so that we know that internal contracts are still valid.

One thought on “Test Doubles: creating a Test Stub

  1. Pingback: Test Doubles: creating a Test Spy – test.right

Leave a Reply

Your email address will not be published. Required fields are marked *