Unit Testing with JUnit – Part 3 – Hamcrest Matchers
2 CommentsLast Updated on June 16, 2019 by Simanta
In this series on unit testing with JUnit, we started with JUnit tests both using Maven and IntelliJ in the first part. In the second part, we learned about assertions, JUnit 4 annotations, and test suites. In this post, we will cover assertThat, a more expressive style of assertion that uses Hamcrest matchers.
Assertions with assertThat
The classic JUnit assertions, such as assertEquals, assertTrue, and so on are simple to understand and use. But, by using assertThat with Hamcrest matchers, It’s easy to make dramatic improvements to your tests. Hamcrest is a framework that provides support for Java unit testing. Hamcrest contains self-contained classes, called matchers with static methods designed to be used with JUnit assertThat.
What you can do with the classic assertions, you can also do with assertThat, but more fluently, and make tests more readable. For example, take a look at the following assertions:
. . . assertFalse(expected.equals(actual)); assertThat(actual, is(not(equalTo(expected)))); . . .
As it is relevant, the second assertion is more readable. If you read out, the second assertion reads more like a sentence – “Assert that actual is not equal to expected”.
Beside test readability, readability of test failures is another highlight of assertThat, as shown in the following figure.
As you can see, the second assertion failure message of assertThat is far more explanatory as compared to assertFalse. It was because we used a core Hamcrest matcher.
Core Matchers
When you write an assertThat method, you pass two parameters to it. The first is the actual result, typically the value/object returned by the method under test. The second parameter is a matcher obtained from a call to a matcher method. The matcher is an object that matches the test rule. To see how it works, we will write a class with few methods that we will test.
MatchersDemo.java
package guru.springframework.unittest.matchers; import java.util.HashSet; import java.util.Set; public class MatchersDemo { public String toConcatedUpperCase(String str1,String str2){ return str1.concat(str2).toUpperCase(); } public double floatingPointMultiplication(double num1, double num2) { return num1 * num2; } private Set<String> stringCol = new HashSet<>(); public boolean addStringToCollection(final String newString) { return this.stringCol.add(newString); } public Set<String> getStringCollection() { return this.stringCol; } }
In the class above, we wrote a toConcatedUpperCase()
method that concatenates two strings passed as parameters, converts the result to uppercase, and returns it. We then wrote a floatingPointMultiplication()
method that returns the product of two double
values passed to it. We also wrote the addStringToCollection()
and getStringCollection()
that adds a string to a Set
collection and returns the Set
respectively.
We will next write a test class with few test methods.
MatchersDemoTest.java
package guru.springframework.unittest.matchers; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; import static org.hamcrest.CoreMatchers.*; public class MatchersDemoTest { MatchersDemo matchersDemo; @Before public void setup(){ matchersDemo=new MatchersDemo(); } @Test public void testToConcatedUpperCase() throws Exception { String expected="HELLOWORLD"; String actual=matchersDemo.toConcatedUpperCase("hello","world"); assertThat(actual, is(equalTo(expected))); } @Test public void testToConcatedUpperCaseStartWith() throws Exception { String expected="HELLO"; String actual=matchersDemo.toConcatedUpperCase("hello","world"); assertThat(actual, startsWith(expected)); } @Test public void testToConcatedUpperCaseContainsString() throws Exception { String expected="WORLD"; String actual=matchersDemo.toConcatedUpperCase("hello","world"); assertThat(actual, containsString(expected)); } @Test public void testToConcatedUpperCaseForAllMatchers() throws Exception { String expected="HELLO"; String actual=matchersDemo.toConcatedUpperCase("hello","world"); assertThat(actual, is(allOf(notNullValue(), instanceOf(String.class), startsWith(expected), containsString(expected)))); } }
In the test class above, we started with a static import of the Hamcrest core matcher, in Line 6 . We then used the @Before
annotation to instantiate the MatchersDemo
class. Recall from the previous post that the @Before
annotated method will run before each @Test
method in the class. Then, we wrote the assertions:
- Line 18: We wrote an
assertThat
with theis
method containing theequalTo
method. We can read it as-“assert that actual (the value that toConcatedUpperCase() method returns) is equal to expected (HELLOWORLD). Although not necessary, many programmers like to useis
and other matcher methods together because it makes assertions more readable. This is the very reason for the existence ofis
and being referred as decorator: to decorate other matchers. - Line 24: We used
startsWith
to assert that the actual string starts with the expected value,HELLO
. - Line 31: We used
containsString
to test that the actual string contains the expected valueWORLD
. - Line 37: We wrote an
assertThat
withallOf
, and things get interesting here.allOf
takes multiple matcher methods and returns a matcher. This matcher test if the actual result matches all the specified matchers- think about the Java short circuit && operator. So, in a single assertion, we asserted that the actual result is not a null value, is an instance of the String class, and starts with and contains HELLO. The corresponding matcher method, that works like the Java short circuit || operator is anyOf.
When we run the test class above, all the test passes. But, the core matchers we used are only a subset of the wide range of Hamcrest matchers. There are additional matchers for specific test requirements, such as testing collections, numbers, text comparison, and so on. The aditional matchers are not part of JUnit, and to use them, we need to seperately download the Hamcrest matcher library and point the project classpath to it. If you are using Maven, add the following dependency to the pom.xml file.
. . . <dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-library</artifactId> <version>1.3</version> <scope>test</scope> </dependency> . . .
Collection Matchers
Collections often have more complex testing needs. For example testing the size of a collection, testing for one or more elements in a collection, their ordering, etc. The Hamcrest collection matchers are designed to support the needs of testing collections in unit tests.
Let’s write a new test class and use the Hamcrest collection matchers.
CollectionMatchersTest
package guru.springframework.unittest.matchers; import org.junit.Before; import org.junit.Test; import java.util.Set; import static org.junit.Assert.*; import static org.hamcrest.Matchers.*; public class CollectionMatchersTest { MatchersDemo matchersDemo; @Before public void setup(){ matchersDemo=new MatchersDemo(); matchersDemo.addStringToCollection("First string"); matchersDemo.addStringToCollection("Second string"); matchersDemo.addStringToCollection("Third string"); } @Test public void testForCollectionSize() throws Exception{ Set<String> actual=matchersDemo.getStringCollection(); int expected=3; assertThat(actual, hasSize(expected)); } @Test public void testForMultipleItemsInCollection()throws Exception{ Set<String> actual=matchersDemo.getStringCollection(); String expected1="First string"; String expected2="Third string"; assertThat(actual, hasItems(expected1, expected2)); } @Test public void testForMultipleItemsWithAnyOrderInCollection() throws Exception{ Set<String> actual=matchersDemo.getStringCollection(); String expected1="First string"; String expected2="Second string"; String expected3="Third string"; assertThat(actual, containsInAnyOrder(expected1, expected2, expected3)); } }
In the test class above, we initialized the Set
collection of MatchersDemo
with few strings in a @Before
method. Then, we wrote the following assertions:
- Line 23: We wrote a
assertThat
withhasSize
to test the size of the collection. - Line 32: We used
hasItems
to test for multiple items in the collection. To test for a single item, you can usehasItem
. - Line 41: We used
containsInAnyOrder
to test that all items in the collection match the expected items, in any order. If you want to test for all items in the same order, use the strictercontains
.
Number Matchers
I particularly find the Hamcrest number matchers useful to test floating point calculations that provide accurate approximations, but not exact results. The assertion assertThat(2.32 * 3, equalTo(6.96));
will fail because the actual result is not what we expect (6.96). By, looking at the failure message we will understand the reason.
java.lang.AssertionError: Expected: <6.96> but: was <6.959999999999999> . . . .
As you can notice, the actual value is different from what we expected. To test for such floating point calculations, there is a closeTo
matcher method that we will cover now.
NumberMatchersTest
package guru.springframework.unittest.matchers; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; import static org.hamcrest.Matchers.*; public class NumberMatchersTest { MatchersDemo matchersDemo; double actual; @Before public void setup(){ matchersDemo=new MatchersDemo(); actual= matchersDemo.floatingPointMultiplication(2.32,3); } @Test public void testFloatingPointMultiplication()throws Exception { assertThat(actual, closeTo(6.96, 0.0005)); } @Test public void testFloatingPointMultiplicationGreaterThan()throws Exception { assertThat(actual, greaterThan(6.0)); } }
In Line 20 of the test class above, we used closeTo
to test for the result of the floatingPointMultiplication()
method under test. The closeTo
method matches if an examined double value is equal to the first parameter value, within a range of +/- error specified by the second parameter. We also wrote an assertThat
with greaterThan
in Line 26 to check if the actual value returned by the method under test is greater than the specified value 6.0
.
Some other number matcher methods are greaterThanOrEqualTo
, lessThan
, and lessThanOrEqualTo
. As their names are self-explanatory, I won’t explain them further.
Text Comparison Matchers
We did some text comparisons with the core matchers on the toConcatedUpperCase()
method earlier in this post. But, for more flexibility, let’s look at some specific text comparison matchers.
TextComparisionMatchersTest.java
package guru.springframework.unittest.matchers; import org.junit.Before; import org.junit.Test; import java.util.Arrays; import static org.junit.Assert.*; import static org.hamcrest.Matchers.*; public class TextComparisionMatchersTest { MatchersDemo matchersDemo; String actual; @Before public void setup(){ matchersDemo=new MatchersDemo(); actual= matchersDemo.toConcatedUpperCase("Hello","World"); } @Test public void testIgnoringCase()throws Exception { assertThat(actual, equalToIgnoringCase("HeLLoWORld")); } @Test public void testIgnoringWhitespace()throws Exception { assertThat(actual, equalToIgnoringWhiteSpace(" HELLOWORLD ")); } @Test public void testContainsInOrder()throws Exception { assertThat("Actual string under test", stringContainsInOrder(Arrays.asList("Actual", "string", "under","test"))); } }
In Line 21 and Line 27 we used equalToIgnoringCase
and equalToIgnoringWhiteSpace
to test for string equality while ignoring casing and white spaces respectively. In Line 33 we used stringContainsInOrder
to test that the actual result contains the specified strings in the same sequence.
The Hamcrest matchers library is big. In this post we looked at few of them. But more importantly, we learned how unit tests are done the real way – Hamcrest matchers.
Summary
As you saw, JUnit’s assertThat combined with Hamcrest matchers has much better functionality. But by saying so, the old assert methods are here to stay. If you are running the old assert methods present in existing test code, you may continue doing so. But, if you plan to write new test code, consider using the Hamcrest matchers. They are more readable – as the JUnit release note says “This syntax allows you to think in terms of subject, verb, object– assert that x is 3”. Also, you will realize the full benefits when your test fails during complex testing. The detailed failure message will point you in the right direction at a very less, or no time at all.
During unit testing enterprise application code using the Spring Framework, you can unleash the potentials of the Hamcrest matchers. Beside the regular assertions, you can use the Hamcrest bean matchers to test properties of Spring beans. You can also use matchers to test whether a view name contains a specific string in Spring MVC, test responses from mock objects. You’ll find the versatility of the Hamcrest matchers very beneficial when writing unit tests in JUnit.
Andy
Thank you for this great tutorial, I like the way you explain it!!!! Thank you
Gopal
Great tutorial