Assume the following simple test:
@Test
public void test() throws Exception {
Object value = 1;
assertThat(value, greaterThan(0));
}
The test won't compile, because "greaterThan" can only be applied to instances of type Comparable
. But I want to assert that value
is an Integer which is greater than zero. How can I express that using Hamcrest?
The simple solution would be to simply remove the generics by casting the matcher like that:
assertThat(value, (Matcher)greaterThan(0));
Possible, but generates a compiler warning and feels wrong.
A lengthy alternative is:
@Test
public void testName() throws Exception {
Object value = 1;
assertThat(value, instanceOfAnd(Integer.class, greaterThan(0)));
}
private static<T> Matcher<Object> instanceOfAnd(final Class<T> clazz, final Matcher<? extends T> submatcher) {
return new BaseMatcher<Object>() {
@Override
public boolean matches(final Object item) {
return clazz.isInstance(item) && submatcher.matches(clazz.cast(item));
}
@Override
public void describeTo(final Description description) {
description
.appendText("is instanceof ")
.appendValue(clazz)
.appendText(" and ")
.appendDescriptionOf(submatcher);
}
@Override
public void describeMismatch(final Object item, final Description description) {
if (clazz.isInstance(item)) {
submatcher.describeMismatch(item, description);
} else {
description
.appendText("instanceof ")
.appendValue(item == null ? null : item.getClass());
}
}
};
}
Feels "tidy" and "correct", but it is really a lot of code for something that seems simple. I attempted to find something like that built-in in hamcrest, but I was not successful, but maybe I missed something?
In my actual test case the code is like this:
Map<String, Object> map = executeMethodUnderTest();
assertThat(map, hasEntry(equalTo("the number"), greaterThan(0)));
In my simplified case in the question I could also write assertThat((Integer)value, greaterThan(0))
. In my actual case I could write assertThat((Integer)map.get("the number"), greaterThan(0)));
, but that would of course worsen the error message if something is wrong.
Introduction. Hamcrest is a framework for writing matcher objects allowing 'match' rules to be defined declaratively. There are a number of situations where matchers are invaluable, such as UI validation or data filtering, but it is in the area of writing flexible tests that matchers are most commonly used.
Purpose of the Hamcrest matcher framework. Hamcrest is a widely used framework for unit testing in the Java world. Hamcrest target is to make your tests easier to write and read. For this, it provides additional matcher classes which can be used in test for example written with JUnit.
assertThat method is deprecated. Its sole purpose is to forward the call to the MatcherAssert. assertThat defined in Hamcrest 1.3. Therefore, it is recommended to directly use the equivalent assertion defined in the third party Hamcrest library.
assertEquals() is the method of Assert class in JUnit, assertThat() belongs to Matchers class of Hamcrest. Both methods assert the same thing; however, hamcrest matcher is more human-readable. As you see, it is like an English sentence “Assert that actual is equal to the expected value”.
This answer will not show how to do this using Hamcrest, I do not know if there is a better way than the proposed.
However, if you have the possibility to include another test library, AssertJ supports exactly this:
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class TestClass {
@Test
public void test() throws Exception {
Object value = 1;
assertThat(value).isInstanceOfSatisfying(Integer.class, integer -> assertThat(integer).isGreaterThan(0));
}
}
No need for any casting, AssertJ does this for you.
Also, it prints a pretty error message if the assertion fails, with value
being too small:
java.lang.AssertionError:
Expecting:
<0>
to be greater than:
<0>
Or if value
is not of the correct type:
java.lang.AssertionError:
Expecting:
<"not an integer">
to be an instance of:
<java.lang.Integer>
but was instance of:
<java.lang.String>
The Javadoc for isInstanceOfSatisfying(Class<T> type, Consumer<T> requirements)
can be found here, which also contains some examples of sligthly more complicated assertions:
// second constructor parameter is the light saber color
Object yoda = new Jedi("Yoda", "Green");
Object luke = new Jedi("Luke Skywalker", "Green");
Consumer<Jedi> jediRequirements = jedi -> {
assertThat(jedi.getLightSaberColor()).isEqualTo("Green");
assertThat(jedi.getName()).doesNotContain("Dark");
};
// assertions succeed:
assertThat(yoda).isInstanceOfSatisfying(Jedi.class, jediRequirements);
assertThat(luke).isInstanceOfSatisfying(Jedi.class, jediRequirements);
// assertions fail:
Jedi vader = new Jedi("Vader", "Red");
assertThat(vader).isInstanceOfSatisfying(Jedi.class, jediRequirements);
// not a Jedi !
assertThat("foo").isInstanceOfSatisfying(Jedi.class, jediRequirements);
The problem is that you lose the type information here:
Object value = 1;
This is an insanely weird line, if you think about it. Here value
is the most generic thing possible, nothing can be reasonably told about it, except maybe checking if it's null
or checking its string representation if it's not. I'm sort of at loss trying to imagine a legitimate use case for the above line in modern Java.
The obvious fix would be assertThat((Comparable)value, greaterThan(0));
A better fix would be casting to Integer
, because you're comparing to an integer constant; strings are also comparable but only between themselves.
If you can't assume that your value
is even Comparable
, comparing it to anything is pointless. If your test fails on the cast to Comparable
, it's a meaningful report that you dynamic casting to Object
from something else failed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With