Reflection Assert

Installation

If you are using maven, you can add following dependency to your project.

01
02
03
04
05
06
<dependency>
    <groupId>org.unitils</groupId>
    <artifactId>unitils-core</artifactId>
    <version>3.4.2</version>
    <scope>test</scope>
</dependency>

If you are not using maven you can download the unitils-with-dependencies.zip. The required jar, unitils-core.jar, can be found in the unitils-core folder, the required dependencies, in the unitils-core/lib folder.

Using reflection for assertion

A typical unit test contains a part in which it compares test result values with expected values. Unitils offers assertion utilities to help you with this. Let's start with an example of comparing 2 User instances having an id, a first and a last name:

01
02
03
04
05
10
11
12
13
14
15
46
public class User {
 
    private long id;
    private String first;
    private String last;
 
    public User(long id, String first, String last) {
        this.id = id;
        this.first = first;
        this.last = last;
    }
}
01
02
03
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertEquals(user1, user2);

You could expect this assertion to be successful since both instances contain the same values. This is however not the case, because User does not override the equals() method. Checking equals of 2 User instances is in that case the same as checking whether both instances are the same. In other words, the assertEquals actually results in user1 == user2 being tested causing the assertion to fail.

Suppose you implemented the equals method as follows:

01
02
03
04
05
06
public boolean equals(Object object) {
    if (object instanceof UserWithEquals) {
        return id == ((UserWithEquals) object).id;
    }
    return false;
}

This is a totally reasonable implementation for your application logic, stating that 2 user instances are referring to the same user when they have the same id. However, this method is not useful in your unit tests. Testing whether 2 objects are equal is now reduced to the test whether they both have the same id:

01
02
03
UserWithEquals user1 = new UserWithEquals(1, "John", "Doe");
UserWithEquals user2 = new UserWithEquals(1, "Jane", "Smith");
assertEquals(user1, user2);

This assertion will be successful, which is probably not what you want. Best is to avoid using equals() altogether when comparing objects (except of course for objects with value semantics such as java.lang.String, ...). One approach can be to do the comparison of each of the properties one by one:

01
02
03
04
05
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertEquals(user1.getId(), user2.getId());
assertEquals(user1.getFirst(), user2.getFirst());
assertEquals(user1.getLast(), user2.getLast());

Unitils offers utilities that help you perform these checks more easily, through reflection. Using ReflectionAssert.assertReflectionEquals, the above example could be re-written as follows:

01
02
03
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertReflectionEquals(user1, user2);

This assertion loops over all fields in both objects and compares their values using reflection. For the above example, it will first compare both id field values, next both first field values and finally both last fields values.

If a field value itself is also an object, it will recursively be compared field by field using reflection. The same is true for collections, maps and arrays. Their elements will be traversed and recursively be compared using reflection. If a value is a primitive type (int, long, ...) or one of the primitive wrapper types (Integer, Long, ...) it will be compared by value (using ==). As a result, following assertions will be successful:

01
02
03
04
05
06
assertReflectionEquals(1, 1L);
 
List<Double> myList = new ArrayList<Double>();
myList.add(1.0);
myList.add(2.0);
assertReflectionEquals(asList(1, 2), myList);

Lenient assertions

For reasons of maintainability, it's important to only add assertions that are of value for the test. Let me clarify this with an example: suppose you have a test for a calculation of an account balance. There is no need to add any assertions to this test that will check the value of the name of the bank-customer. This will only add complexity to your test making it more difficult to understand and, more importantly, more brittle against changes to the code. If you want your test code to easily survive refactorings and other code changes, make sure you limit your assertions and test data setup to the scope of the test.

To help you in writing such assertions we added some levels of leniency to the ReflectionAssert checks. We'll cover these leniency levels in the sections that follow.

Lenient order

A first type of leniency you can specify is ignoring the order of elements in a collection or array. When working with lists, you are often not interested in the actual order of the elements. For example, code for retrieving all bank-accounts with a negative balance will typically return them in an order that is unimportant for the actual processing.

To implement this behavior, the ReflectionAssert.assertReflectionEquals method can be configured to ignore ordering by supplying it the ReflectionComparatorMode.LENIENT_ORDER comparator mode:

01
02
List<Integer> myList = asList(3, 2, 1);
assertReflectionEquals(asList(1, 2, 3), myList, LENIENT_ORDER);

Ignoring defaults

A second type of leniency is specified by the ReflectionComparatorMode.IGNORE_DEFAULTS mode. When this mode is set, java default values, like null for objects and 0 or false for values are ignored. In other words, only the fields that you instantiate in your expected objects are used in the comparison.

As an example, suppose you have a user class with a first name, last name, street... field, but you only want to check that the resulting user instance has a certain first name and street value, ignoring the other field values:

01
02
03
11
12
User actualUser = new User(1, "John", "Doe", new Address("First street", 12, "Brussels"));
User expectedUser = new User(1, "John", null, new Address("First street", null, null));
assertReflectionEquals(expectedUser, actualUser, IGNORE_DEFAULTS);
assertReflectionEquals("message", null, anyObject, IGNORE_DEFAULTS);  // Succeeds
assertReflectionEquals(anyObject, null, IGNORE_DEFAULTS);  // Fails

You specify that you want to ignore a field by setting this value to null in the left (=expected) instance. Right-instance fields that have default values will still be compared.

01
02
assertReflectionEquals("message", null, anyObject, IGNORE_DEFAULTS);  // Succeeds
assertReflectionEquals(anyObject, null, IGNORE_DEFAULTS);  // Fails

Lenient dates

A third type of leniency is ReflectionComparatorMode.LENIENT_DATES. This will assert that the date field values in both instances are both set or both equal to null; the actual values of the dates are ignored. This can be useful if you want to do strict checking on the fields of objects (without using ReflectionComparatorMode.IGNORE_DEFAULTS), but there are fields in your object set to the current date or time that you want to ignore.

01
02
03
Date actualDate = new Date(44444);
Date expectedDate = new Date();
assertReflectionEquals(expectedDate, actualDate, LENIENT_DATES);

assertLenientEquals

The ReflectionAssert class also offers an assertion for which two of the leniency levels, lenient order and ignore defaults are pre-set: ReflectionAssert.assertLenientEquals. The above examples can therefore be simplified as follows:

01
02
03
04
05
List<Integer> myList = asList(3, 2, 1);
assertLenientEquals(asList(1, 2, 3), myList);
 
assertLenientEquals(null, "any");  // Succeeds
assertLenientEquals("any", null);  // Fails

This len/ref thing is a general naming convention: assertReflection... is the version that is strict by default and for which you can manually set the leniency levels, assertLenient... is the version for which the lenient order and ignore defaults are pre-set.

Property assertions

The assertLenientEquals and assertReflectionEquals methods compare objects as a whole. ReflectionAssert also contains methods to compare a specific property of two objects: assertPropertyLenientEquals and assertPropertyReflectionEquals. The actual field that needs to be compared is specified using the OGNL notation. This language supports typical bean property expressions, like field1.innerField and a number of more powerful constructions.

Some examples of property comparisons are:

01
02
assertPropertyLenientEquals("id", 1, user);
assertPropertyLenientEquals("address.street", "First street", user);

You can also supply collections as arguments for this method. In that case the specified field will be compared on each element in the collection. This provides an easy way to for example check whether all users in a retrieved list have certain id:

01
02
assertPropertyLenientEquals("id", asList(1, 2, 3), users);
assertPropertyLenientEquals("address.street", asList("First street", "Second street", "Third street"), users);

Again there are two versions of each method: assertPropertyReflectionEquals and assertPropertyLenientEquals . The ref version doesn't have leniency specified by default but provides the option to specify leniency modes, the len version has lenient order and ignore defaults set as fixed leniency modes.