Creating custom Hamcrest matchers 2
Published the
Hello, and welcome to the second part of this series of posts on Creating custom Hamcrest matchers.
In this part I'll show how to create matchers that wrap up other matchers. I'll use as example a matcher that will verify the string representation of any object. This matcher will have to be able to wrap other matchers and integrate them in its own description.
In the last part, the divideBy matcher only accepted numbers as argument, this time the asString matcher will accept only strings and matchers as arguments. Basically this matcher will take any value and casting it to a String (implicitly it will call the toString method of this object) to then verify the resulting string.
Testing our stuff
Because it's all about unit testing, this matcher will not derogate to tests.
test( "asString matcher", function(){
// we just want string or matchers as argument
assertThat( asString, throwsError() );
assertThat( asString, throwsError().withArgs( {} ) );
assertThat( asString, throwsError().withArgs( null ) );
var anObjectWithAToStringMethod = {
'toString':function(){ return "foo"; }
}
// testing call with string as argument
assertThat( 10, asString( "10" ) );
assertThat( false, asString( "false" ) );
assertThat( {}, asString( "[object Object]" ) );
assertThat( anObjectWithAToStringMethod,
asString( "foo" ) );
// testing call with a matcher as argument
assertThat( 10, asString( matchRe( "\\d+" ) ) );
assertThat( false, asString( equalTo( "false" ) ) );
assertThat( {}, asString( contains( "object" ) ) );
assertThat( anObjectWithAToStringMethod,
asString( startsWith("fo") ) );
// testing some cases where asString must return false
assertThat( 10, not( asString( "5" ) ) );
assertThat( anObjectWithAToStringMethod,
asString( not( startsWith( "[" ) ) ) );
});
Now it's time to implement the concret behavior of the matcher.
As you can see in the tests, the matcher can be called either with a string or a matcher. In fact, calling asString with a string will be the same as doing asString( equalTo( aString ) ).
The description message, both in the describeTo and the matches call, should integrate the matcher's descriptions as well. A good description should be constructed to be valid when the asString is used as matcher in another compound matcher as well.
Let's see what the concret matcher will look like :
function asString ( m ){
// check argument
if( m == null )
throw "asString expect an argument";
else if( typeof m != "string" &&
!(m instanceof Matcher) )
throw "asString argument must be either a string or a Matcher object";
// implicit equalTo
if( typeof m == "string" )
m = equalTo( m );
return new Matcher({
// storing the matcher as property
'matcher':m,
'_matches':function(v,msg){
// just set a prefix to the message
msg.appendRawValue("toString").appendText("returns");
// in that case we accept all values
// and just pass the value casted as String
// to the child matcher
return this.matcher.matches( String(v), msg );
},
'_describeTo':function(msg){
// compose the expected message
// with the matcher description
msg.appendText( "a value of which" )
.appendRawValue("toString")
.appendText("returns")
.appendDescriptionOf(this.matcher);
},
});
}
And here the result :
This post conclude this series on hamcrest for now. I hope you've learned something and that it give you envy to give it a try.
This post is part of the "Hamcrest For QUnit" series.
Previous post in series : Creating custom Hamcrest matchers
Wants to leave a word ?