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.
4 comments:
I think you're missing one thing: you are absolutely encouraged to create your own matchers by extending TypeSafeMatcher or TypeSafeDiagnosingMatcher (preferably the latter, as it gives better error messages). I would generally recommend that you use MatcherAssert.assertThat over the Assert class whenever possible, as the error messages are much clearer. If you find yourself using assertTrue, maybe move one step up and assert that a property on the object you're working with is true; that way, you'll get a message such as "the property 'foo' was false" rather than just: "was false".
You'll also find many more matchers if you include the hamcrest-library JAR as well as the JUnit-bundled hamcrest-core. Take a look at the org.hamcrest.Matchers class for details. And then go write your own!
Peter
Thanks for the Astrology routines. I am a professional Astrologer. Real time chart dynamics is one idea I would like to pursue. I envision a SOLAR TIME Clock Watch that gives both real time and solar (degree position) time. I also have concept dynamics of Lottery Prediction using numbers and Planetaries. Would you like to expand or cooperate? Best; Peter aka paneagle..... email is paneagle(seven)7
Peneagle,
funny that you post that comment here instead of the page of the astro source code - never mind ;-)
I assume you mean the Java Astro Library that I published way back in 2007, but had created much earlier in time (around 2000 or so). It was extracted from my Zodiac Watch Applet which was Java 1.0 compliant. (OMG this stuff is old!)
Back then I wanted to have a clock, more exact a wrist watch, that would display the current time, solar time and zodiac. Obviously it was not possible. With the rise of smart phones including GPS this should not be too difficult today. But I moved on to other areas and do not work with this project any more.
Peneagle, thank you for offering cooperation, but I am busy with other projects right now that are far more interesting to me. Good luck!
Two weeks ago Samir told me that things have changed with JUnit 4.11. Since that version Assert is delegating to MatcherAssert:
public static <T> void assertThat(String reason, T actual,
Matcher<? super T> matcher) {
MatcherAssert.assertThat(reason, actual, matcher);
}
Post a Comment