Part 2 of my testing rant – Hamcrest!
I love hamcrest because it makes my life easier. Instead of writing loops to verify a collection or horrible string matches regex in a assertTrue() method, I can use hamcrest to write a clean and concise assertion. When you write an assertion using hamcrest, it reads like a simple statement; “Assert that value is not null” or “Assert that collection has item”.
Of course there are plenty of other articles and tutorials that cover how to use hamcrest, but there are several features that are not covered particularity well by any of them and it can be hard to find documentation on some of the more advanced matchers.
Hamcrest Basics
The heart of hamcrest is the assertThat() method. The assertion accepts a value and a set of matchers, if the matchers can be applied to the value (i.e., they match) then the assertion passes. You’ll use this assertion for all your hamcrest tests as the assert relies on the matchers you supply to define the check and validation.
The most basic matcher is equalTo(), or it’s shorter, easier to read alias is(). The equalTo() matcher can be used just like the old assertEquals() assertion, but it’s power will really shine through later when combined with other matchers.
import org.testng.annotations.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
@Test
public class HamcrestTest {
@Test
public void equality {
String value = "abc";
assertThat(value, equalTo("abc"));
assertThat(value, is("abc"));
}
}
Logical Matchers
One of the nice things about hamcrest matchers is that you can use some logical operators to modify or group multiple matchers under a single assertion.
not()– negate a matcherallOf()– pass assertion if all matcher conditions passanyOf()– pass assertion if any matcher conditions pass
@Test
public void logicalOperators {
String value = "abc";
// pass if condition does not match
assertThat(value, not(is("123")));
// pass if all of the conditions match
assertThat(value, allOf(startsWith("a"), endsWith("c"));
// pass if any of the conditions match
assertThat(value, anyOf(is("abc"), is("def"), is(not("123")));
}
Text Pattern Matchers
With the hamcrest-text library you can build pattern matching expressions for complex string matching using the PatternMatcher. Text matching is also included with hamcrest-all, but beware that it’s omitted from the matchers that are included with TestNG.
You might be thinking that a Regular Expression can already be used for this purpose, but what what you gain in a regex power you lack in it’s clarity (you maybe strong in the Regex-fu, but what about your co-workers?). On the other hand, a hamcrest matcher is VERY clear and easy to read.
import org.testng.annotations.Test;
import org.hamcrest.text.pattern.PatternMatcher;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.text.pattern.PatternMatcher.*;
import static org.hamcrest.text.pattern.Patterns.*;
@Test
public class PatternMatcherTest {
PatternMatcher alphanumeric = new PatternMatcher(
oneOrMore(
either(anyCharacterIn("0-9"),
anyCharacterIn("A-Z"),
anyCharacterIn("a-z"),
)
)
);
@Test
public void textMatching {
// string matches alphanumeric pattern
assertThat("abc123", matchesPattern(alphanumeric));
// string does NOT match alphanumeric pattern
assertThat("abc 123 !@#$", not(matchesPattern(alphanumeric));
}
}
Collection Matchers
The trick with collection matchers is that you can’t use the static imports with matchers that have a generic type. To get the full effect, you need to use the fully qualified path with the generic type on the Matcher classes static method.
@Test
public void collections() {
List list = Arrays.asList("a string", "another", "more strings");
// list contains "a string"
assertThat(list, Matchers.<String>hasItem("a string"));
// list contains one of many possible items
assertThat(list, anyOf(Matchers.<String>hasItem("a string"), Matchers.<String>hasItem("splort!"));
}
JavaBeans Property Matchers
JavaBeans property matchers are useful for verifying properties of complex types for matching. It’s exceptionally useful to verify the properties of complex types in a list, where normally you would need to iterate through the entire collection.
The syntax for the hasProperty() matcher is different than your average matcher too. The matcher itself takes the name of the property and another matcher for verifying the value.
public class Car {
private String make;
private String colour;
// getters and setters omitted ...
}
@Test
public void bean() {
Car car = new Car("Subaru", "blue");
assertThat(car, Matchers.<Car>hasProperty("make", is("Subaru"));
}
@Test
public void beanList() {
List<Car> cars = Arrays.asList(new Car("Subaru", "blue"), new Car("Mazda", "red"));
// list of cars contains a Subaru
assertThat(cars, hasItem(Matchers.<Car>hasProperty("make", is("Subaru")));
// list of cars contains a car that's red
assertThat(cars, hasItem(Matchers.<Car>hasProperty("colour", is("red")));
}
