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
Now let us compare the actual source code. JUnit's
Assert.assertThat
looks likepublic 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 ispublic 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.