Test Doubles: creating a Dummy Object manually

In this series of posts about Test Doubles we’ll look at five examples. The Dummy Object is the first and most straightforward. Its definition is:

A placeholder object that is passed to the SUT as an argument (or an attribute of an argument) but is never actually used

We sometimes need to pass an argument to a method just to make our test compile, but the argument itself is irrelevant to the test. Simply putting in null as a dummy will do the trick if the type of the argument allows that. But it severely uglifies and obscures our test. Instead we should strive to make self-describing tests that won’t leave our colleagues guessing what the intent of arguments (and the tests) is.

If you want, you can clone / download the examples used in this post on github. In this example, we test adding a line item on a CheckInSheet for a pawnshop. But we have some setup to do.

  • In order to instantiate the check-in sheet we need a Pawner.
  • The Pawner requires us to make an Address.
  • The Address needs a City.
  • The City cannot be made without a Province.

A test without a dummy object

@Test
public void check_in_sheet_should_add_line_item() {
  final int NUMBER_OF_ITEMS = 1;
  Asset asset = new Asset(getUniqueText());      
    
  // four lines of unnecessary setup  
  Province province = new Province("Zuid Holland", "ZH");      
  City city = new City("The Hague", province);
  Address address = new Address("Spui 138", city, "2525 TT");
  Pawner pawner = new Pawner(getUniqueText(), getUniqueText(), address);
    
  CheckInSheet checkInSheet = new CheckInSheet(pawner);     

  checkInSheet.add(asset);

  List lineItems = checkInSheet.getLineItems();
  assertEquals("number of items on the check in sheet", NUMBER_OF_ITEMS, lineItems.size());
  assertEquals("the right item on the check in sheet", asset, lineItems.get(0));
}

So many lines just to create a Pawner that isn’t even used by the SUT in this test! We can delete half of the test if we use a dummy object.

Invert the dependency
To start doing this, let’s look at the signature of the constructor of CheckInSheet.

public CheckInSheet(Pawner pawner)

Apparently, the Dependency Inversion Principle has not been applied. So we’ll have to create an interface before we can make a dummy pawner. We can use our refactoring menu in the class Pawner: Refactor → Extract Interface. Let’s call this interface Customer. This is the result.

public interface Customer {
  String getFullName();
  Address getAddress();
}

Now we can change the signature of the constructor of CheckInSheet by using Refactor → Change (Method) Signature.

public CheckInSheet(Customer customer)

It’s ready to accept anything that implements Customer.

Create a dummy object
Let’s make our Dummy Object based on the interface.

public class DummyPawner implements Customer {

  @Override
  public String getFullName() {
    return null;
  }
  
  @Override
  public Address getAddress() {
    return null;
  }

}

It’s almost ready to use. We should make the dummy object tell us when it’s used by the SUT.

public class DummyPawner implements Customer {

  @Override
  public String getFullName() {
    throw new RuntimeException("The Dummy Pawner should never be used by the SUT!");
  }
  
  @Override
  public Address getAddress() {
    throw new RuntimeException("The Dummy Pawner should never be used by the SUT!");
  }

}

A test with a dummy object
Let’s have our test use this DummyPawner.

public void check_in_sheet_should_add_line_item() {
  final int NUMBER_OF_ITEMS = 1;
  Asset asset = new Asset("Dummy Asset Name");            
  CheckInSheet checkInSheet = new CheckInSheet(new DummyPawner());     
    
  checkInSheet.add(asset);
    
  List lineItems = checkInSheet.getLineItems();
  assertEquals("number of items on the check in sheet", NUMBER_OF_ITEMS, lineItems.size());
  assertEquals("the right item on the check in sheet", asset, lineItems.get(0));
}

Instead of having four lines of code to create a Pawner, we now instantiate DummyPawner inline. This is much more clear and to the point than before. Notice also, that we’ve replaced getUniqueText() with "Dummy Asset Name", a self-describing value that indicates the asset name is irrelevant to the test.

A test violating the use of a dummy object
CheckInSheet has a toString() method which uses the Customer object with which it was instantiated. So to prove that our DummyPawner is in fact a dummy object, let’s write this test.

@Test(expected = RuntimeException.class)
public void reject_dummy_when_used_in_SUT() throws Exception {
  new CheckInSheet(new DummyPawner()).toString();
}

The test passes! Our dummy truly is a dummy.

A final word on the manual approach
As you can see, it takes quite some work to get a simple dummy object setup. It also gives us more code to maintain. That’s why the manual approach is not the recommended way. The next post will focus on creating a dummy object using a mocking framework.

Author: David Baak

To code or not to code? That's true.

Leave a Reply

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