
I regularly meet online with friends and
practice remote pair programming. Last time, while working with my friend
Thomas, one of us mistyped the static import for
assertThat
and we ended with an import of
org.hamcrest.MatcherAssert
instead of
org.junit.Assert
. I had never seen
MatcherAssert
before and Thomas asked me if I knew what the actual difference between them would be. I did not know but we are going to find out right now.
A little bit of history
The
Hamcrest Matchers are part of JUnit 4 since version 4.4. JUnit up to version 4.10 shipped with Hamcrest 1.1 and on release 4.11 it switched to Hamcrest 1.3. Looking at the history of
MatcherAssert
it seems that it had been in the core already but had been moved out again before version 1.1 was released.
- May 22, 2007 - Moved
assertThat()
into hamcrest-core. - May 23, 2007 - Moved
assertThat()
back into hamcrest-integration (really this time). - July 19, 2007 - JUnit 4.4 shipped including Hamcrest 1.1
- November 25, 2008 - Moved
MatcherAssert
to core. - July 9, 2012 - Hamcrest version 1.3 released
- November 14, 2012 - shipped JUnit 4.11
Show me the code!
Now let us compare the actual source code. JUnit's
Assert.assertThat
looks like
public static <T> void assertThat(T actual, Matcher<T> matcher) {
assertThat("", actual, matcher);
}
public static <T> void assertThat(String reason, T actual,
Matcher<T> matcher) {
if (!matcher.matches(actual)) {
Description description = new StringDescription();
description.appendText(reason);
description.appendText("\nExpected: ");
description.appendDescriptionOf(matcher);
description.appendText("\n got: ");
description.appendValue(actual);
description.appendText("\n");
throw new AssertionError(description.toString());
}
}
whereas Hamcrest's
MatcherAssert
code is
public static <T> void assertThat(T actual, Matcher<? super T> matcher) {
assertThat("", actual, matcher);
}
public static <T> void assertThat(String reason, T actual,
Matcher<? super T> matcher) {
if (!matcher.matches(actual)) {
Description description = new StringDescription();
description.appendText(reason)
.appendText("\nExpected: ")
.appendDescriptionOf(matcher)
.appendText("\n but: ");
matcher.describeMismatch(actual, description);
throw new AssertionError(description.toString());
}
}
public static void assertThat(String reason, boolean assertion) {
if (!assertion) {
throw new AssertionError(reason);
}
}
assertThat(String, boolean)
The most obvious difference is that Hamcrest defines an additional
assertThat
method which accepts a boolean expression. The method is identical to
Assert.assertTrue(String, boolean)
. No big deal.
<? super T>
The signatures of both classes are almost identical. Almost because Hamcrest has a variation of the generic type
T
. JUnit accepts matchers that have the
same type as the actual value in the argument list
T actual, Matcher<T> matcher
, while Hamcrest also accepts them for super types of
T
in
T actual, Matcher<? super T> matcher
. Allowing parent types of
T
makes sense because what matches the parents of
T
will always match
T
too. Here is an example of a
Matcher<Number>
matching against an
Integer
which extends
Number
.
import org.hamcrest.CustomMatcher;
...
@Test
public void shouldAllowSuperTypeMatcherInAssert() {
Integer actual = 3;
Matcher<Number> matcher = new CustomMatcher<Number>("anything") {
@Override
public boolean matches(Object anything) {
return true;
}
};
MatcherAssert.assertThat(actual, matcher); // (1)
Assert.assertThat(actual, matcher); // (2)
}
Line
(1) binds
<T>
to
<Integer>
because
<? super Integer>
allows
Number
as type of the given matcher. On the other hand, line
(2) compiles as well, setting
<T>
directly to
<Number>
. So the two signatures are equivalent.
Description of the Mismatch
The third difference is the way the two assertions describe the mismatch. While JUnit just appends the actual value with
description.appendValue(actual)
, Hamcrest asks the matcher to describe its mismatch by
matcher.describeMismatch(actual, description)
, which might give more information in case of failure. So let us dig into some more code.
@Test
public void shouldDisplaySimilarMessageForIsMatcher() {
int actual = 1;
Matcher<Integer> matcher = is(2);
assertJUnitMessage(actual, matcher,
"Message\nExpected: is <2>\n got: <1>\n");
assertHamcrMessage(actual, matcher,
"Message\nExpected: is <2>\n but: was <1>");
}
As the
BaseMatcher
's
describeMismatch
just returns
"was " + actual
there is no real difference in the output of both assertions. What about other matchers? Let us look for implementations of
describeMismatch
that do more than just return the actual value. The only matcher I found was
org.hamcrest.TypeSafeMatcher
, which has several subclasses in the Hamcrest library, e.g.
HasProperty
,
IsEmptyIterable
and
IsCloseTo
.
import org.hamcrest.beans.HasProperty;
import org.hamcrest.collection.IsEmptyIterable;
import org.hamcrest.number.IsCloseTo;
...
@Test
public void shouldDisplaySimilarMessageForHasPropertyMatcher() {
Object actual = "abc";
Matcher<Object> matcher = HasProperty.hasProperty("prop");
assertJUnitMessage(actual, matcher,
"Message\nExpected: hasProperty(\"prop\")\n got: \"abc\"\n");
assertHamcrMessage(actual, matcher,
"Message\nExpected: hasProperty(\"prop\")\n but: no \"prop\" in \"abc\"");
}
@Test
public void shouldDisplaySimilarMessageForIsEmptyIterableMatcher() {
Iterable<String> actual = Collections.<String> singletonList("a");
Matcher<Iterable<String>> matcher = IsEmptyIterable.<String> emptyIterable();
assertJUnitMessage(actual, matcher,
"Message\nExpected: an empty iterable\n got: <[a]>\n");
assertHamcrMessage(actual, matcher,
"Message\nExpected: an empty iterable\n but: [\"a\"]");
}
@Test
public void shouldDisplaySimilarMessageForIsCloseToMatcher() {
double actual = 2.0;
Matcher<Double> matcher = IsCloseTo.closeTo(1, 0.1);
assertJUnitMessage(actual, matcher,
"Message\nExpected: a numeric value within <0.1> of <1.0>\n got: <2.0>\n");
assertHamcrMessage(actual, matcher,
"Message\nExpected: a numeric value within <0.1> of <1.0>\n but: <2.0> differed by <0.9>");
}
Hamcrest creates slightly more detailed error messages, but only for these three cases.
Conclusion
org.hamcrest.MatcherAssert
is not a replacement for
org.junit.Assert
because it does not come with all the assertions which we are used to. But
MatcherAssert
contains a slightly different
assertThat
. Using that method could potentially give better error messages because the matcher is called to describe the mismatch. When using a complex, custom matcher this could improve the error messages and speed up error diagnosis. Currently only three matchers implement their own descriptions. These come with the Hamcrest library which is not included with JUnit right now.