Generics, constructors, arguments of class Class 
Friday, October 13, 2006, 20:54
A common case when writting generic classes is when one need to instantiate the type passed as a type parameter. Unfortunatelly in Java it is not possible stright away. One way to achieve this is to pass a coresponding Class to the object, and usual place to do this is a constructor. The generic class in its simpliest form may look like:

public class MyGenericClass <T> {
public MyGenericGroup(Class<T> cls) {}
}

And to instantiate the whole generic class you need to type:

new MyGenericClass<MyType>(MyType.class);

As we can see there are two places where you pass same (in a way) type information - as a type parameter and as a class of that type. A nice way to avoid this is to make use of a generic static factory method that acts as a bridge and "extracts" the type information from the Class instance given. Our generic class may look like:

public class MyGenericClass <T> {
private MyGenericClass(Class<T> cls) {}
public static <E> MyGenericClass<E> getInstance(Class<E> cls) {
return new MyGenericClass<E>(cls);
}
}

The instantiation is then a little bit simplier and more readible:

MyGenericClass.getInstance(MyType.class);

  |  permalink
EasyMock and test failures 
Sunday, August 20, 2006, 18:07
After using EasyMock for a few weeks it usually turns out that explanations given in case of test failures (unexpected calls to mock objects) are useless and you need to debug the test to see the real reason of failure. In 90% of cases the reason is argument's value mismatch during comparison to expected argument. You can assure better faiulre explanations with the following:

(1) implementing toString() in classes used as arguments

(2) correctly implementing appendTo(StringBuffer) in your matcher

Unfortunatelly implementing appendTo(StringBuffer) means creating a text representation of matcher's logic already written in matches(Object). At this point it's quite obvious that there should be a way to make it a little more intelligent. Moreover in this scenario failure explanations still don't give you the most precize information - you need to investigate to see which argument didn't match (often by analysing argument's properties values, one by one).

My solution is an argument matcher builder. Matchers created with this class contain generic implementation of matches(Object) and appendTo(StringBuffer). There is one common method to configure both matcher's logic and string representation - append(String, Object). Also, the text appended to StringBuffer contains information about exact property which caused the test to fail. A sample implementation is as simple as the following:


import java.util.ArrayList;
import java.util.List;

import org.apache.commons.beanutils.PropertyUtils;
import org.easymock.EasyMock;
import org.easymock.IArgumentMatcher;

public class ArgumentMatcherBuilder<T> {
private static class Expectation {
private String propertyName;

private Object expectedValue;

public Object getExpectedValue() {
return expectedValue;
}

public String getPropertyName() {
return propertyName;
}

public Expectation(String propertyName, Object expectedValue) {
this.propertyName = propertyName;
this.expectedValue = expectedValue;
}
}

private static class FailedExpectation extends Expectation {
private Object actualValue;

public FailedExpectation(String propertyName, Object expectedValue,
Object actualValue) {
super(propertyName, expectedValue);
this.actualValue = actualValue;
}

public Object getActualValue() {
return actualValue;
}

}

private final List<Expectation> expectations = new ArrayList<Expectation>();

private final List<FailedExpectation> failedExpectations = new ArrayList<FailedExpectation>();

public ArgumentMatcherBuilder<T> append(String propertyName,
Object expectedValue) {
expectations.add(new Expectation(propertyName, expectedValue));
return this;
}

public T registerMatcher() {
EasyMock.reportMatcher(new IArgumentMatcher() {
final public void appendTo(final StringBuffer buffer) {
buffer.append("<");
for (FailedExpectation failedExpectation : failedExpectations) {
buffer.append(failedExpectation.getPropertyName()
+ "[exp: "
+ failedExpectation.getExpectedValue().toString()
+ " ; act: " + failedExpectation.getActualValue()
+ "] ");
}
buffer.append(">");
}
final public boolean matches(Object obj) {
failedExpectations.clear();
boolean match = true;
for (Expectation expectation : expectations) {
Object actualValue;
try {
actualValue = PropertyUtils.getProperty(obj,
expectation.getPropertyName());
} catch (Exception e) {
throw new RuntimeException(
"Problem while accessing property, " +
"check property name, " +
"getter signature and access modifier",
e);
}
if (!actualValue.equals(expectation.getExpectedValue())) {
match = false;
failedExpectations.add(new FailedExpectation(
expectation.getPropertyName(), expectation
.getExpectedValue(), actualValue));
}
}
return match;
}
});
return null;
}
}

  |  permalink