Hamcrest Matcher Builder for Beans

279 views
Skip to first unread message

Yoav Abrahami

unread,
Jan 2, 2012, 7:08:48 AM1/2/12
to Hamcrest Developers
Hi Hamcrest Developers,

We are Wix using Hamcrest extensively, mainly the Java version and now
starting also using the JS version. While we find Hamcrest very
helpful, we found that writing custom matchers for beans take too
match effort.
We have implemented an alternative solution using MatcherBuilder
interface and JDK Proxy - the details are explained at this post
http://www.wix.com/engineering/home/apps/blog/matcher-builder-for-hamcrest

Basically, it allows writing assertions like
assertThat(person, Person()
.withFirstName("John")
.withLastName("Smith")
.withAddress(Address()
.withCity("New York")
.withStreetAddress(containsString("42nd"))
.withZip(12345)
.matcher())
.matcher());

with the minimal amount of coding possible with Java - by just
introducing two interfaces and two factory methods (those interfaces
and factory methods are listed in the post above).


We are thinking on contributing this solution to the Hamcrest project.
Do you like the idea?
Do you want to adopt it?
If so, what's the process with Hamcrest?

Cheers,
Yoav

David Harkness

unread,
Jan 2, 2012, 9:32:10 PM1/2/12
to hamcre...@googlegroups.com
On Mon, Jan 2, 2012 at 4:08 AM, Yoav Abrahami <yoa...@gmail.com> wrote:
Basically, it allows writing assertions like
assertThat(person, Person()
     .withFirstName("John")
     .withLastName("Smith")
     .withAddress(Address()
         .withCity("New York")
         .withStreetAddress(containsString("42nd"))
         .withZip(12345)
         .matcher())
      .matcher());

This is pretty cool. I have two minor suggestions that might make the assertions more readable:

1. Name the static factory methods isXXX (e.g. isPerson). While you can always wrap it using is() -- is(Person()...) -- it was confusing at first seeing Person as a type and a method name.

2. Have the builder implement Matcher with matches() delegating to the actual matcher. This would remove the need for the calls to matcher().

Nice work!

Steve Freeman

unread,
Apr 25, 2012, 7:36:06 AM4/25/12
to hamcre...@googlegroups.com
This is nice. We're moving across to JavaHamcrest on GitHub. Could you clone and send me a pull request?

Incidentally, I've introduced new implementation concepts of Condition and Step that might be helpful. See if it works for you.

S.

> --
> You received this message because you are subscribed to the Google Groups "Hamcrest Developers" group.
> To post to this group, send email to hamcre...@googlegroups.com.
> To unsubscribe from this group, send email to hamcrest-dev...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/hamcrest-dev?hl=en.
>

Steve Freeman

Winner of the Agile Alliance Gordon Pask award 2006
Book: http://www.growing-object-oriented-software.com

+44 797 179 4105
Twitter: @sf105
Higher Order Logic Limited
Registered office. 2 Church Street, Burnham, Bucks, SL1 7HZ.
Company registered in England & Wales. Number 7522677

Dominic Fox

unread,
Apr 25, 2012, 11:51:04 AM4/25/12
to hamcre...@googlegroups.com
This overlaps significantly with the MagicJavaBeanMatchers we've been developing at TIM Group:


There are a couple of handy things our version does that you might like to consider implementing, if you don't already:

  * Matching on getXXX() properties and on accessible public fields, defaulting to the getter where available.
  * Supporting a range of idioms for method name -> property name mapping, e.g. having_a_foo_of, withTheFoo and with_a_foo all map to foo.
  * Overriding the method name in the interface with an annotation, e.g. @AddressesProperty("dateOfBirth") BeanMatcher born_on(Date date)
  * Allowing custom behaviour for method calls with multiple parameters or other special needs, e.g.:

(in the interface)
    @AddressesProperty("name") @MatchesWith(ForenameSurnameMatcher.class)
    public PersonMatcher named(String forename, String surname);

(class implementing the custom behaviour)
    public static final class ForenameSurnameMatcher implements MethodCallInterpreter<Matcher<?>> {
        @Override
        public Matcher<?> apply(MethodCall methodCall) {
            String fullName = Joiner.on(" ").join(methodCall.argumentValues());
            return JavaBeanPropertyMatcherMaker.forAnyClass()
                                               .make(methodCall, Matchers.equalTo(fullName));
        }
    }

We also have some MagicRecordBuilders in the same library, which go together quite well with the Matchers:


Best wishes,
Dominic Fox

Reply all
Reply to author
Forward
0 new messages