I was reading the article New Tricks with Dynamic Proxies in Java 8 from Dominic Fox and I thought that could be interesting to implement the code as he suggests.
Source code
The source code used in this post can be found at my GitHub repository:
https://github.com/mroger/hamcrest-proxy-matcher
Model
The Person model class is a very simple POJO to demonstrate the use of the proxy matcher and can be seen below.
The test
The test method we're interested in uses a custom matcher implemented using dynamic proxy.
The aPerson() method creates a proxy for PersonMatcher and returns its fluent interface as seen below. The interface declares the methods used to setup the matcher.
The custom matcher
The idea behind this custom matcher proxy is to intercept all the calls to the PersonMatcher interface and provide their implementation on the fly. The custom matcher's users will only have to comply to one little rule: PersonMatcher interface methods have to start with "with".
The first methods called on PersonMatcher will be the ones defined by the interface. In the invoke method, those method names and arguments are put in the map for later processing. You can see it below.
Then, Hamcrest calls the custom matcher's matches() method and the proxy intercepts it, using the method names previously stored in the map to extract values from the actual object using reflection and comparing them with the also previously stored values. Note that the "with" prefix is removed from method names to obtain the fields names that, in conjunction with expected and actual values, are used to assemble expectedDescription and mismatchDescription, printed in the last two phases in Hamcrest´s life cycle.
Lastly, if any of the values doesn't match, then Hamcrest also calls the Matcher's describeTo() and describeMismatch() method, to give the custom matcher a chance to explain, as a formatted description, what values diverge, using descriptions stored previously.
The first methods called on PersonMatcher will be the ones defined by the interface. In the invoke method, those method names and arguments are put in the map for later processing. You can see it below.
Then, Hamcrest calls the custom matcher's matches() method and the proxy intercepts it, using the method names previously stored in the map to extract values from the actual object using reflection and comparing them with the also previously stored values. Note that the "with" prefix is removed from method names to obtain the fields names that, in conjunction with expected and actual values, are used to assemble expectedDescription and mismatchDescription, printed in the last two phases in Hamcrest´s life cycle.
Lastly, if any of the values doesn't match, then Hamcrest also calls the Matcher's describeTo() and describeMismatch() method, to give the custom matcher a chance to explain, as a formatted description, what values diverge, using descriptions stored previously.
As we can see, using dynamic proxy is a great way to create semantics and DSLs for your tests. It also helps to avoid tedious implementations.
Wanna share your thoughts? I´d like very much to know your opinions about this and if it helped you in any way.
Wanna share your thoughts? I´d like very much to know your opinions about this and if it helped you in any way.