Hamcrest is a library for creating matchers for usage in unit tests, mocks and UI validation. This talk gives a brief introduction to using and writing Hamcrest matchers.
The topics covered:
* Basic introduction to Hamcrest
* Using Matchers in assertions
* Using Matchers with Mockito
* Writing custom matchers
* Ad-hoc matchers
2. What is Hamcrest?
According to the project homepage,
[Hamcrest] provides a library of matcher objects (also known
as constraints or predicates) allowing 'match' rules to be
defined declaratively, to be used in other frameworks. Typical
scenarios include testing frameworks, mocking libraries and
UI validation rules.
Hamcrest is not a testing library: it just happens that
matchers are very useful for testing.
3. Typical usage example
Actual value
assertThat(
someString,
is(equalTo(someOtherString))
);
Expectation on the
value, represented as
a Matcher
4. Wait – but what’s wrong with
assertEquals?
assertEquals(someString, someOtherString)
This looks just as good, doesn’t it?
assertEquals(
cat.getName(),
otherCat.getName())
Not so bad, either
5. However,
What about collections?
assertEquals(someKitten,
cat.getKittens().iterator().next())
This works if our kitten is the first element in the
collection, but what about asserting that the kitten
exists anywhere in the collection?
6. Well, we can do this:
boolean found = false;
for (Kitten kitten : cat.getKittens()) {
if (kitten.equals(someKitten)) found = true;
}
assertTrue(found);
7. But don’t you prefer this?
Iterable<Kitten>
assertThat(
cat.getKittens(),
hasItem(someKitten))
Matcher on
Iterable<Kitten> that
accepts a Matcher<Kitten>
9. Basic Matchers
A Matcher is initialized with the expected
values, which are compared against the
actual object we’re matching against when
invoking the matcher.
10. IsEqual Matcher
class IsEqual<T> extends BaseMatcher<T> {
private final Object object; // c’tor omitted for readability
public boolean matches(Object arg) {
return object.equals(arg);
}
}
11. StringEndsWith Matcher
class StringEndsWith <T> extends BaseMatcher<T> {
private final String suffix; // c’tor omitted for readability
public boolean matches(String s) {
return s.endsWith(suffix);
}
}
13. Mockito in one slide
CatShop catShop = mock(CatShop.class);
when(catShop.purchase(somePurchaseRequest
).thenReturn(someCat) Creating the mock
... do some work Stubbing the mock
verify(catShop) Behavior verification
.purchase(expectedPurchaseRequest)
14. Without Hamcrest
CatPurchase catPurchase = new CatPurchase();
catPurchase.setBreed(“Brittish”);
when(catShop.purchase(catPurchase))
).thenReturn(somePreviouslyStubbedCat)
However, this will force us to set all other fields of the
CatPurchase class, since Mockito will perform an exact match
comparison between our instance and the actual one
15. Of course, you could do this:
when(
catShop.purchase(
any(CatPurchaseDTO.class))
).thenReturn(somePreviouslyStubbedCat)
This works, but lacks the benefit of asserting that our
operation is only valid for the expected input
16. The solution: use argThat()
Mockito helper that creates
an argument matcher from
when( a Hamcrest matcher
catShop.purchase(argThat(
hasPropertyWithValue(
“breed”,
startsWith(“Brittish”))))
).thenReturn(somePreviouslyStubbedCat)
Hamcrest matcher that
accepts a Java Bean
property name and a
nested value matcher
17. Using for behavioral verification
CatDao catDao = mock(CatDao.class);
CatStore catStore = new CatStore (catDao);with a Catcall to
Verify that there was a
CatDao.update() instance,
catStore.saveOrUpdate(existingCat); the ‘name’ property is
for which
“felix” and the ‘kittens’ property is an
verify(catDao).update(argThat( Iterable containing two kittens,
kitten1 and kitten2
allOf(
hasPropertyWithValue(“name”, “felix”),
hasPropertyWithValue(“kittens”,
hasItems(kitten1, kitten2)))));
19. Writing your own matchers
In the previous examples, we used the
hasPropertyWithValue() matcher, which, while
allowing for fluent assertions or stubbing, has
the disadvantage of not being type-safe.
This is where writing custom matchers becomes
useful (or, as some would say, necessary).
21. Dissecting some Wix matchers
class HostMatcher extends TypeSafeMatcher<WixUrl> {
private final Matcher<String> host; // c’tor omitted for readability
public boolean matchesSafely(WixUrl url) {
return host.matches(url.host); Nested matcher that
} will be replayed on the
Actual value being
actual value
matched against
public void describeTo(Description description) {
description.appendText("Host that matches ").appendValue(host); we write a
Here
} readable description of
our expected value
public static HostMatcher hasHost(Matcher<String> host)A utility factory method for
{
return new HostMatcher(host); fluently creating this
} matcher. Not mandatory
} but very convenient.
22. Using our matcher
WixUrl url =
new WixUrl(“http://www.wix.com/some/path”);
assertThat(url, hasHost(is(“www.wix.com”))); ✔
assertThat(url, hasHost(endsWith(“wix.com”))); ✔
assertThat(url, hasHost(is(“google.com”))); ✗
java.lang.AssertionError:
Expected: Host that matches <is ”google.com">
got: <http://www.wix.com/some/path>
23. Another URL matcher
class WixUrlParamMatcher extends TypeSafeMatcher<WixUrl> {
private final Matcher<String> name; // c’tor omitted for readability
url.params is a Map<String, String>, so
private final Matcher<String> value;
we create a matcher for a map entry
around our name and value matchers
public boolean matchesSafely(WixUrl url) { replay it against the actual value
and
return hasEntry(name, value).matches(url.params);
}
public void describeTo(Description description) {
description
.appendText("Param with name ").appendValue(name)
.appendText(" and value ").appendValue(value);
}
}
24. Using the two matchers together
String s = “www.wix.com?p=v1&p=v2&p3=v3”;
WixUrl url = new WixUrl(s);
assertThat(url, allOf(
hasHost(is(“www.wix.com”)),
hasParam(is(“p”), anyOf(is(“v1”), is(“v2”))),
hasParam(is(“p3”), startsWith(“v”))));
25. But wait – my URL is a String!
Sometimes you’ll have matchers that accept a specific
type, such as WixUrl or XML Document. For this
purpose, use a wrapping matcher that performs the
conversion for you:
class StringAsUrlMatcher extends TypeSafeMatcher<String> {
private final Matcher<WixUrl> urlMatcher;
public boolean matchesSafely(String urlString) {
return matcher.matches(new WixUrl(urlString));
}
public void describeTo(Description description) {
description.appendText("Url that matches ")
.appendDescriptionOf(urlMatcher);
}
}
27. Consider the following class
class Proxy {
private final HttpClient httpClient;
private String targetUrl;
public String handle (String path) {
httpClient.execute(// some HttpGet);
}
}
28. The HttpClient interface
public HttpResponse execute(HttpGet get);
Our class under test is expected to replace the domain
in path with targetUrl, thus serving as an HTTP Proxy.
We would like to stub and verify the HttpGet parameter
to make sure it builds the proxy URL properly.
29. My test looks something like this
HttpClient client = mock(HttpClient.class);
String url = “http://www.example.com/”;
{…} handler = new Proxy(client, url);
when(client.execute({www.a.com/path}))
.thenReturn(someResponse);
handler.handle(“www.a.com/path”);
verify(client).execute({www.example.com/path});
30. The solution
Matcher<HttpGet> HttpGet(final Matcher<String> urlMatcher) {
return new TypeSafeMatcher<HttpGet>() {
public boolean matchesSafely(HttpGet httpGet) {
return urlMatcher.matches(httpGet.getURI().toString()));
}
public void describeTo(Description description) {
description.appendText("HttpGet with url ")
.appendDescriptionOf(urlMatcher);
}
};
}
31. Usage of the HttpGet matcher
when(handler.execute(argThat(
is(HttpGet(startsWith(“http://www.a.com”))))))
.thenReturn(response);
handler.handle(“http://www.a.com/some/path”);
verify(client).execute(argThat(is(HttpGet(
is(“http://www.example.com/some/path”)))));
32. The plot thickens
Moments after triumphantly running the test I
realized that in addition to verifying that the
request went to the appropriate URL, I had to
verify that some – but not all – HTTP headers
were copied to the proxy request and some new
ones were added to it.
33. Ad-hoc matchers to the rescue
1) Add the following parameter to the HttpGet
method:
final Matcher<Header[]> headersMatcher
2) Change the matchesSafetly() method:
public boolean matchesSafely(HttpGet httpGet) {
return urlMatcher.matches(httpGet.getURI().toString())
&& headersMatcher.matches(httpGet.getAllHeaders());
}
34. Ad-hoc matchers to the rescue
3) Write a matcher for the Header class:
Matcher<Header> Header(
final Matcher<String> name, final Matcher<String> value) {
return new TypeSafeMatcher<Header>() {
public boolean matchesSafely(Header header) {
return name.matches(header.getName())
&& value.matches(header.getValue());
}
}
}
35. Putting it all together
verify(client).execute(argThat(is(HttpGet( that the X-Wix-Base-Uri header
Asserts
contains the expected value (using the
is({URL matcher omitted for readability}),
WixUrl matchers we’ve seen before).
allOf(
hasItemInArray(
Header(
is("X-Wix-Base-Uri"),
Asserts that there’s no header by
isUrlThat( the name of X-Seen-By, no matter
what value it has
hasHost(“www.wix.com”), hasPath(myPath)))),
not(hasItemInArray(
Header(is("X-Seen-By"), any(String.class))))
)))));
This is a simplified version of the actual matcher from Hamcrest Core
For those of you who’re not familiar with Mockito, here’s how you use it
This has the disadvantage of being type-unsafe, but we’ll get back to this point later on.
Like the previous example, this is type-unsafe because we’re not sure that the Cat class even has a field named “kittens”, let alone its type
Matcher is the common ancestor for all Hamcrest matchers. Note that I removed a warning urging you not to implement Matcher<T>, extending BaseMatcher<T> instead.In most cases, you’ll want to extend TypeSafeMatcher<T>
This matcher works on the WixUrl class, which is a type-safe URL builder class from the Wix infrastructure.It’s a good example for the simple, yet somewhat confusing, structure of a matcher. Note that this matcher has one member, which is another string matcher for specifying the expected value. We could’ve used a string here, but using a Matcher<String> allows us to be more flexible when using this matcher.This pattern of replaying a matcher against the actual value is a core principle of Matcher programming.Note that our matcher extends TypeSafeMatcher, which implements the Matcher.matches() method and delegates to the matchesSafely() method after performing a type assertion followed by a cast.
Why do we need a Url matcher? Sometimes we don’t care about the query string, the host, the port, the protocol, etc. We could’ve used a string matcher such as contains() or endsWith() but this is more type safe, readable and illustrates the tested behavior more clearly.Note how the assertion error is much more readable than the one produced by assertEquals()
This one takes two matchers, one for the parameter name and one for the parameter value.Note another principle introduced here, that of creating a new matcher wrapping our matchers and replaying it against the actual value. Thus, we abstract away from the test the fact the WixUrl.params is a map. In fact, we lately changed it from a Map<String, String> to a Multimap<String, String> without breaking any test – I just changed the field type and the appropriate matcher class.
Note the use of the allOf() and anyOf() aggregate matchers that represent boolean AND and OR operations, respectively.
Based on a true storyThis class accepts an instance of HttpClient (Apache HttpComponents 4.0). I wanted to give it a mock of HttpClient, then stub and verify it.
I needed to be able to write matchers for the HttpGet class, for two reasons:1) It was much more readable than constructing an instance of it2) HttpGet takes on parameter, URI, and contains logic that deals with the HTTP protocol itself – not something that’s relevant for the test
Note, again, how we replay the expected value matcher against the actual value. This prevents the need to construct our own instance on HttpGet and populate it with the expected value. Also note that this is a factory method, creating an anonymous class. The method starts with a capital letter and is named according to the name of the class we’re matching against. This is a convention we developed here at Wix, but we find it useful and clear.
The use case here is that the proxy should send all traffic from www.wix.com/ to theproxied server, appending any nested path.Note that response can be either a real object constructed beforehand or a mock, but the mock must also be constructed before calling the when() method because of some Mockito limitation.Also note the reuse of our matcher both for stubbing the mock and verifying the desired behavior.
The matcher is for a Header array because HttpGet.getAllHeaders() returns an array
Note that without using Hamcrest, there’s no straightforward way to test for the inexistence of a value