Test infrastructure, part 3 - Matching with Hamcrest

A word on Hamcrest, a library which integrates with JUnit to improve test readability.

When checking if two objects obj1 and obj2 are equals, the "classic" JUnit way is to write something like:

import org.junit.Test;

class Test

   @Test
   public void equalsTest(){  
  
      assertEquals(ob1, obj2);
      //is obj1 the expected or actual result ? 
      //not clear without resorting to the javadoc 
   }
}

with Hamcrest - the test reads (almost) like plain ordinary english and it is easier to see that obj1 is the actual result and obj2 is the expected result:

import org.junit.Test;
import static org.hamcrest.MatcherAssert.assertThat; 
import static org.hamcrest.Matchers.is;

class Test{

   @Test
   public void equalsTest(){
      //asserts that the actual result is what's expected.
      assertThat (obj1, is(obj2));
   }
}


The above illustrates the use of the is() matcher, there are many other matchers to choose from... Just to name a few:

//hasEntry matcher
Map aMap = new HashMap ();
amap.put("test",123);
assertThat (aMap, hasEntry("test",123);

//greaterThan matcher
assertThat(stuff.size(), greaterThan(0));

//combining is and not matchers
assertThat (stuff.size(), is(not(0)))x

//assert that an array contains certain elements
String [] array1 =....;
assertThat (array1, allOf(hasItemInArray("element1"), hasItemInArray("element2")));

Test infrastructure, part 2 - randomizing data

The builder created in part 1 initialises the domain object under test with predefined values:

 id {123} and name {defaultName}

public class DomainObjectBuilder{
   private Integer id="123";
   private String name="defaultName";

   public DomainObject build(){
      return new CustomObject(id,name);
   }
   ....
}

The objects thus constructed by the builder are then used in tests. So far so good. Sometimes though we want these tests to work a little harder, and prove that they can run ok with different input values. The methods below can be used to that end, building on the RandomStringUtils and RandomUtils methods from Apache Commons.

eg. to randomise a string by adding a 2 digits to the end of it.
public String randomizedString(String aString){
   return aString+RandomStringUtils.randomAlphaNumeric(2);
}
and the builder becomes (assuming the randomizedString method is statically imported)
public class DomainObjectBuilder{
   private Integer id=randomizedString("123");
   private String name=randomizedString("defaultName");

   public DomainObject build(){
      return new CustomObject(id,name);
   }
   ....
}
Each invocation of the DomainOBjectBuilder.build() method will now return domain objects with slightly different values. Of course it's not only strings which can be randomized. The same principles applies to enums:
public static <T extends Enum> T randomizedEnum (class <T> enumClass){
   T[] enumConstants = enumClass.getEnumConstants();
  return enumConstants[RandomUtils.nextInt(enumConstants.length);
}
... and to lists.
public static <T>  T randomElementFromList(List aList){
   return aList.get(RandomUtils.nextInt(aList.size()));   
}

Test infrastructure, part 1 - building the builders

The basic brick of a test infrastructure is the ability to easily instantiate and set properties on the domain object under test. This can be achieved with a builder object, as per below. This builder exposes a fluent interface (in the chaining of methods withId and withName) to improve readibility.


Given a domain object such as

public class DomainObject{

   public DomainObject (Integer id, String name){
     this.id = id;
     this.name = name;
   }

   public Object doSomething(){
      ...
   }
}

then the associate builder will be:
public class DomainObjectBuilder{
   private Integer id="123";
   private String name="defaultName";

   public DomainObject build(){
      return new CustomObject(id,name);
   }

   public DomainObjectBuilder withId(Integer anId){
      this.id = anId;
      return this;
   }

   public DomainObjectBuilder withName(String name){
      this.name = name;
      return name;
   }
}
and a test for a DomainObject will look like:

DomainObject anObject = new DomainObjectBuilder()
.withId("345").withName("someTest").build();
assertThat (anObject.doSomething, is(anExpectedResult));

An alternative approach would be to do without a builder and simply add setters on the domain object, setters which would be invoked during the tests. The major drawback of this technique is that adding setters breaks immutability.