Export to GitHub

hamcrest - issue #199

Use of expressions to check "hasProperty" in beans


Posted on May 28, 2013 by Massive Horse

Hi,

I would like to see an enhancement to matchers for beans so that they can support "expressions" like OGNL (http://commons.apache.org/proper/commons-ognl/) or Spring EL (http://static.springsource.org/spring/docs/3.0.x/reference/expressions.html).

Motivation: In a complex POJO, the usage of expressions could allow to test more easily complex POJOs. Syntax would look like the following:

assertThat(myBean, hasPropertyByExp(expression, valueMatcher)

The matcher is similar to hasProperty(String propertyName, Matcher<?> valueMatcher) but here the "expression" parameter is a String that represents a full expression which allow querying the object graph of the bean.

As a result, asserts could be more compact and so easier to read and understand (see the following example).

Example:

Suppose MyBean is a class with a Map<String,MyOtherBean> field named "map" and MyOtherBean is a class with a List<String> field named list. And it is populated like the following:

Mybean myBean = new MyBean();
myBean.setMap(new HashMap());
myBean.getMap().put(&quot;myKey&quot;, new MyOtherBean());
myBean.getMap().get(&quot;myKey&quot;).setList(Arrays.asList(&quot;one&quot;, &quot;two&quot;, &quot;three&quot;));

then, if you want to test if "three" is equal to the third element of the "list" of the instance of MyOtherBean stored under the key "myKey" in the map "map" of the object "myBean", you could write a single line assert in the following way:

assertThat(myBean, hasPropertyByExp(&quot;map['myKey'].list[3]&quot;, equalsTo(&quot;three&quot;));

Technology: I actually use Spring EL to write tests based on expression on beans. I retrieve the value using an expression and I simply compare it with "assertEquals".

I've written the following utility static method based on Spring EL:

import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser;

//...

public static <T> T getValue(Object pojo, String expression, Class<T> t) { ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression(expression); return exp.getValue(pojo, t); }

If the test is embedded in a matcher, it would make the code even more readable.

General considerations to be done before implementation: It should be considered the impact of dependencies using either Spring EL or OGNL or other expression languages (my choice of using Spring EL has been taken due to the fact I already have Spring dependencies on my code).

Considering that Spring EL is made also for manipulating the Object Graph, and for testing purposes only query of the object graph is relevant, it could be feasible also to implement a limited Expression Language specifically for Hamcrest, and avoid external dependencies.

The following link points to the Spring EL syntax reference: http://static.springsource.org/spring/docs/3.0.x/reference/expressions.html#expressions-language-ref

In particular, the most meaningful language feature of the Spring EL for writing matchers is the syntax for querying "Properties, Arrays, Lists, Maps, Indexers" as is described in section 6.5.2 of the previous link. Maybe this could be implemented in Hamcrest with a little reflection and a good regex to parse the expression.

Final note: I've searched this feature in Hamcrest and other frameworks without success, if I've missed it, please don't blame me and please give me a link to the solution. I've tried to write the most precise report for the enhancement request, if there is something which is not clear, please ask clarification in the comments.

Thank you. Cristiano

Comment #1

Posted on May 28, 2013 by Massive Hippo

(No comment was entered for this change.)

Comment #2

Posted on Jan 27, 2015 by Happy Camel

Cristiano,

In my own search for a similar feature today I found I was able to satisfy my requirement via a combination of the org.hamcrest.beans.HasPropertyWithValue matcher and nested org.hamcrest.Matchers matchers.

For your example above, I came up with two possible assertThat's that may satisfy your requirement, depending exactly what you need to check in the list sub-property.

First idea:

    Assert.assertThat(
            "Expected myBean.map['myKey'].list to equal [\"one\", \"two\", \"three\"]",
            myBean,
            HasPropertyWithValue.<MyBean> hasProperty(
                    "map",
                    IsMapContaining.<String, MyOtherBean> hasEntry(
                            Matchers.equalTo("myKey"),
                            HasPropertyWithValue.<MyOtherBean> hasProperty("list",
                                    Matchers.contains("one", "two", "three")))));

Second idea - perhaps closer to your original pseudocode's intent:

    // assertThat(myBean, hasPropertyByExp("map['myKey'].list[3]", Matchers.equalTo("three")));
    Assert.assertThat(
            "Expected myBean.map['myKey'].list[3] to equal \"three\"",
            myBean,
            HasPropertyWithValue.<MyBean> hasProperty(
                    "map",
                    IsMapContaining.<String, MyOtherBean> hasEntry(
                            Matchers.equalTo("myKey"),
                            HasPropertyWithValue.<MyOtherBean> hasProperty(
                                    "list",
                                    Matchers.contains(Matchers.anything(), Matchers.anything(),
                                            Matchers.equalTo("three"))))));

Does either of these do the job well enough for your need?

Hopefully putting the above in the public record helps us and other future travellers of this Java testing path. :)

Mark

Status: New

Labels:
Java