http://www.developer.com/java/article.php/3901236/Write-More-Understandable-Java-Tests-with-Matcher-Objects-and-FEST-Assert.htm
When writing Java unit tests, developers often face the problem of their unit test assertions being long and hard to read. For example, consider the following JUnit test: This JUnit test case has a This code is rather hard to read and understand, isn't it? Wouldn't it be great if you could do all this testing in one line of code? In this article, I will demonstrate how to use a library of matchers for building test expressions. The library is called Hamcrest and it contains lots of helpful matcher objects. It helps you specify matching rules which you can use for unit testing. Let’s rewrite the above JUnit test using Hamcrest library. In this test method, we use the It asserts that As you can see, the test case written using Hamcrest is much more readable as it communicates its intention very clearly. It can be read as follows: assert that every word in the words collection should start with "av" and end with "e. " As you start using matchers, you will learn how easy it is to write assertions that are human readable and easy to understand. They will just become a part of your unit testing toolkit. The two main benefits of using matchers in your unit tests are: Let’s modify the previous example by adding another word "aver" in our list and then rerun the test. Now, both test cases will fail but they will emit different failure traces. Without Hamcrest Matchers Using Hamcrest Matchers As you can see, the failure trace emitted by Hamcrest is much more descriptive and tells you where the problem is. Any developer can very easily debug the problem using this failure information. Now I will talk about why you should use matchers in your code. Let’s take a look at matcher support in JUnit. JUnit comes bundled with a subset of Hamcrest matchers so you don't have to add any new dependency in your application. These matchers can be found in the It can also be used to match arrays (including multidimensional), for example: It can also be used with multidimensional arrays as follows: JUnit extended these basic matchers and created some custom matchers, which can be found in the So far we have used only a subset of Hamcrest, which comes bundled with JUnit. To get the full power of Hamcrest we have to use the Hamcrest library. You can download the Here are some of the most important matchers that do are not available in the JUnit library. Let’s look at some of these matchers more closely: While Hamcrest contains lots of built-in matchers, sometimes you will need to write your own matcher to fit your testing needs. (This Hamcrest tutorial explains how to write your own matchers, and you will find a number of Hamcrest extension libraries here.) FEST-Assert is a Java 5 library that provides a fluent interface for writing assertions. Both Hamcrest and FEST-Assert aim to improve the readability and maintainability of tests, but they are implemented in a different way. FEST-Assert uses chaining to build complex assertions whereas Hamcrest uses nesting. Let’s compare a test written using Hamcrest with one written using FEST. Using Hamcrest Using FEST-Assert With Hamcrest, you cannot nest two matchers (i.e. FEST-Assert is undoubtedly much easier to use than Hamcrest but it doesn't come bundled with JUnit and you can't write assertions that can test the elements of a collection. For example, you can’t write the test shown below using FEST-Assert. The FEST-Assert API, which provides a fluent interface for writing assertions, is very rich and easy to use. It has a similar feel to mocking frameworks such as jMock and EasyMock. To start using FEST, you need to either download it or add its dependency in your Maven POM file. FEST-Assert provides assertions for the following data types: Let’s rewrite some of the test cases from earlier in the article using matchers with FEST-Assert. Both Hamcrest and FEST-Assert do a great job of improving Java test readability and maintainability, but each has some limitations. I personally use both of them in my project to get the full power of both. Try them out and write more productive unit tests. Shekhar Gulati is a Java consultant with over 5 years of experience. He is currently working with Xebia India, an Agile software development company. He writes his personal blog at whyjava.wordpress.com.
Write More Understandable Java Tests with Matcher Objects and FEST-Assert
August 30, 2010
private List words;
@Before
public void setUp() {
words = Arrays.asList("available", "avenge", "avenue", "average");
}
@Test
public void shouldContainAllElementsStartingWithavAndEndingWithe() {
for (String word : words) {
assertTrue(word.startsWith("av") && word.endsWith("e"));
}
}setUp method (annotated with the @Before annotation) that will initialize some data for the test, and then it has a test method. To test that all the elements in the collection start with av and end with e, the code first iterates over the loop and then uses two assertions (starting with av and ending with e). This test method also includes some boilerplate code, which iterates over a loop and then fires assertions on each element.Benefits of Using Matchers in Java Tests
@Test
public void shouldContainAllElementsStartingWithavAndEndingWithe() {
assertThat(words, everyItem(both(startsWith("av")).and(endsWith("e"))));
}assertThat method with the Hamcrest matchers nested within each other. Nesting is one of Hamcrest’s powerful features because it allows you to build complex assertions. The assertThat method has the following signature:
public static void assertThat(T actual, Matcher matcher)actual satisfies the condition specified by matcher and will throw an AssertionError if the actual does not satisfy the condition.
java.lang.AssertionError:
at org.junit.Assert.fail(Assert.java:91)
at org.junit.Assert.assertTrue(Assert.java:43)
at org.junit.Assert.assertTrue(Assert.java:54)
at com.shekhar.junit.JUnitMatchersTest.shouldContainAllElementsStartingWithavAndEndingWithe(JUnitMatchersTest.java:43)
java.lang.AssertionError:
Expected: each (a string starting with "av" and a string ending with "e")
got: <[available, avenge, avenue, average, avert]>
at org.junit.Assert.assertThat(Assert.java:778)
at org.junit.Assert.assertThat(Assert.java:736)
at com.shekhar.junit.JUnitMatchersTest.shouldContainAllElementsStartingWithavAndEndingWithe(JUnitMatchersTest.java:50)The Matchers Provided in the JUnit Library
org.hamcrest.core package of the JUnit JAR. Let’s take a look at some of these matchers:
equals method for testing equality, for example:
assertThat("shekhar", equalTo("shekhar"));
String[] s1 = {"a", "b"};
String[] s2 = {"a", "b"};
assertThat(s2, equalTo(s1));
int[][] i1 = new int[][]{{1, 2}, {3, 4}};
int[][] i2 = new int[][]{{1, 2}, {3, 4}};
assertThat(i2, equalTo(i1));assertThat(name, is(equalTo("shekhar")))
It can be read as A is not equal to B.assertThat("A",not("B"))
assertThat(null,nullValue());
assertThat("A",notNullValue());true if all of the passed matchers evaluate to true. AllOf uses shortcut evaluation, so subsequent matchers are not called if an earlier matcher evaluate to false. For example:
assertThat(list, allOf(notNullValue(),hasSize(1)));
assertThat(1, instanceOf(Number.class));
assertThat(null, not(instanceOf(String.class)));org.junit.matchers package. All the matchers are hosted in a JUnitMatchers class. Let’s look at some of these matchers with a unit test.
import static org.junit.matchers.JUnitMatchers.*;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import org.junit.Before;
import org.junit.Test;
public class JUnitMatchersTest {
private List words;
@Before
public void setUp() {
words = Arrays.asList("available", "avenge", "avenue", "average");
}
/**
* This method tests hasItem matcher which takes an element. This matcher
* matches any collection matching an element.
*/
@Test
public void shouldMatchACollectionContainingAElement() {
assertThat(words, hasItem("available"));
}
/**
* This method tests hasItems matcher which takes multiple elements. This
* matcher matches any collection containing every element in the elements.
*/
@Test
public void shouldMatchCollectionContainingMultipleItems() {
assertThat(words, hasItems("available", "avenge", "avenue"));
}
/**
* This method tests hasItem matcher which takes a matcher. This matcher
* matches any collection containing an element matching elementMatcher
*/
@Test
public void shouldMatchCollectionContainingElementMatcher() {
assertThat(words, hasItem(IsEqual.equalTo("available")));
}
/**
* This method tests hasItems matcher which takes multiple matchers.
*/
@SuppressWarnings("unchecked")
@Test
public void shouldMatchCollectionContainingElementMatchers() {
assertThat(
words,
hasItems(containsString("avail"), containsString("ave"),
containsString("average")));
}
/**
* This method tests everyItem matcher. This matcher matches any collection
* in which every element matches elementMatcher.
*/
@Test
public void shouldHaveEveryItemMatchingElementMatcher() {
assertThat(words, everyItem(containsString("av")));
}
}Getting the Full Power of Hamcrest
hamcrest-all-1.3.0RC1.jar from code.google.com. This JAR is not present in any Maven repository so you have to manually install it in your Maven repository.
mvn install:install-file -Dfile=hamcrest-all-1.3.0RC1.jar -DgroupId=org.hamcrest -DartifactId=hamcrest-all -Dversion=1.3.0RC1 -Dpackaging=jar
hasProperty -- Tests JavaBeans properties
array -- Tests an array's elements against an array of matchershasEntry, hasKey, hasValue -- Test a map that contains an entry, key or valuehasItemInArray -- Test an array that contains an element
closeTo -- Tests whether floating point values are close to a given valuegreaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo -- Test ordering
equalToIgnoringCase -- Tests string equality, ignoring caseequalToIgnoringWhiteSpace -- Tests string equality, ignoring differences in runs of whitespacecontainsString, endsWith, startsWith -- Test string matching
HasPropertyWithValue : -- This matcher asserts that a JavaBean property on an argument passed to the mock object meets the provided matcher.
@Test
public void testHamcrestLibrary() {
TeamMate actual = new TeamMate("shekhar", "gulati");
assertThat(actual, hasProperty("firstName",
IsEqual.equalTo("shekhar")));
List team = Arrays.asList(new TeamMate("shekhar", "gulati"),new TeamMate("rahul", "sharma"),new TeamMate("sameer", "arora"));
Matcher withFirstName = hasProperty("firstName", IsEqual.equalTo("shekhar"));
assertThat(team, hasItem(withFirstName));
}IsCollectionWithSize: This matcher checks whether the collection size satisfies a given matcher.
assertThat(list, hasSize(1));IsMapContaining: This matcher is for map.
@Test
public void testHamcrestMapMethods() {
Map map = new HashMap();
map.put("shekhar", "Shekhar Gulati");
map.put("test", "test123");
assertThat(map, hasEntry(equalTo("shekhar"), equalTo("Shekhar Gulati")));
assertThat(map, hasEntry(equalTo("test"), equalTo("test123")));
assertThat(map, not(hasEntry(equalTo("shekhar"), equalTo("test123"))));
}IsIterableContainingInAnyOrder and IsIterableContainingInOrder -- These matchers are used to test whether iterable should be in any order or use natural ordering. You will need to check the use of comparator and compareTo.
@Test
public void testIsIterableContainingInAnyOrderAndIsIterableContainingInOrder(){
List list = Arrays.asList("a","b","c");
assertThat(list, contains("a","b","c"));
assertThat(list, contains(equalTo("a"),equalTo("b"),equalTo("c")));
assertThat(list, containsInAnyOrder("c","a","b"));
}
@Test
public void testHamcrestLibraryIsIterableContainingInOrder() {
TeamMate shekhar = new TeamMate("shekhar", "gulati");
TeamMate rahul = new TeamMate("rahul", "sharma");
TeamMate sameer = new TeamMate("sameer", "arora");
List team = Arrays.asList(shekhar, rahul, sameer);
assertThat(team, IsIterableContainingInOrder.contains(new TeamMate(
"shekhar", "gulati"), new TeamMate("rahul", "sharma"),
new TeamMate("sameer", "arora")));
}Hamcrest vs. FEST-Assert
@Test
public void shouldMatchCollectionHasSizeAndContainsMultipleItems() {
List words = Arrays.asList("available", "avenge", "avenue", "average");
assertThat(words, hasItems("available", "avenge", "avenue"));
assertThat(words,hasSize(3));
}
@Test
public void shouldMatchCollectionHasSizeAndContainsMultipleItems() {
List words = Arrays.asList("available", "avenge", "avenue", "average");
assertThat(words).hasSize(3).contains("available", "avenge", "avenue");
}hasItems and hasSize), whereas with FEST-Assert you can chain the assertions together. Whether you should use Hamcrest or FEST-Assert depends on your style of programming, but I find FEST-Assert easier to. I prefer FEST-Assert because:
assertThat) and depending upon your parameter the library will give you a set of methods to work on. This reduces the conceptual weight of FEST-Assert..) and you will get the possible assertion methods for the value passed to assertThat.
@Test
public void shouldContainAllElementsStartingWithavAndEndingWithe() {
assertThat(words, everyItem(both(startsWith("av")).and(endsWith("e"))));
}The FEST-Assert API
org.easytesting
fest-assert
1.3
ObjectStringCollectionMapboolean, int, char, etc.) ObjectBufferedImageThrowableFileBigDecimal
public class FESTAssertTest {
private List words;
@Before
public void setUp() {
words = Arrays.asList("available", "avenge", "avenue", "average");
}
@Test
public void shouldMatchACollectionContainingAElement() {
assertThat(words).isNotEmpty();
assertThat(words).hasSize(4);
assertThat(words).isNotNull();
assertThat(words).contains("available");
assertThat(words).hasSize(4).isNotEmpty();
}
@Test
public void compareTwoArrays(){
String[] s1 = {"a", "b"};
String[] s2 = {"a", "b"};
assertThat(s1).isEqualTo(s2);
}
@Test
public void testAShouldNotBeEqualToB(){
assertThat("A").isNotEqualTo("B");
}
@Test
public void shouldMatchCollectionContainingMultipleItems_FEST() {
assertThat(words).contains("available", "avenge", "avenue");
}
@Test
public void testFestAssertionLibrary(){
assertThat(0).isZero();
TeamMate shekhar = new TeamMate("shekhar", "gulati",26);
TeamMate rahul = new TeamMate("rahul", "sharma",27);
TeamMate sameer = new TeamMate("sameer", "arora",28);
List team = Arrays.asList(shekhar, rahul, sameer);
assertThat(team).contains(new TeamMate("shekhar", "gulati"));
assertThat(team).onProperty("firstName").contains("shekhar");
assertThat(team).isNotEmpty();
assertThat(team).hasSize(3).contains(shekhar,rahul,sameer);
assertThat(team.get(0)).isInstanceOf(TeamMate.class).isEqualTo(shekhar).isNotEqualTo(rahul);
}
}Quick Tip for Eclipse Users: If you are using Eclipse you can use to shortcut Ctrl+Shift+M (Add Import) to statically import a method. You can also add static members to Java / Editor / Content Assist / Favorites that allow code completion on static methods.
Conclusion
About the Author