/*  Copyright (c) 2000-2006 hamcrest.org
 */
package org.hamcrest.beans;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;

import org.hamcrest.Condition;
import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeDiagnosingMatcher;

import static org.hamcrest.Condition.matched;
import static org.hamcrest.Condition.notMatched;
import static org.hamcrest.beans.PropertyUtil.NO_ARGUMENTS;

Matcher that asserts that a JavaBean property on an argument passed to the mock object meets the provided matcher. This is useful for when objects are created within code under test and passed to a mock object, and you wish to assert that the created object has certain properties.

Example Usage

Consider the situation where we have a class representing a person, which follows the basic JavaBean convention of having get() and possibly set() methods for it's properties:
public class Person {
  private String name;
  public Person(String person) {
    this.person = person;
  }
  public String getName() {
    return name;
  }
}
And that these person objects are generated within a piece of code under test (a class named PersonGenerator). This object is sent to one of our mock objects which overrides the PersonGenerationListener interface:
public interface PersonGenerationListener {
  public void personGenerated(Person person);
}
In order to check that the code under test generates a person with name "Iain" we would do the following:
Mock personGenListenerMock = mock(PersonGenerationListener.class);
personGenListenerMock.expects(once()).method("personGenerated").with(and(isA(Person.class), hasProperty("Name", eq("Iain")));
PersonGenerationListener listener = (PersonGenerationListener)personGenListenerMock.proxy();
If an exception is thrown by the getter method for a property, the property does not exist, is not readable, or a reflection related exception is thrown when trying to invoke it then this is treated as an evaluation failure and the matches method will return false.

This matcher class will also work with JavaBean objects that have explicit bean descriptions via an associated BeanInfo description class. See the JavaBeans specification for more information:

http://java.sun.com/products/javabeans/docs/index.html
Author:Iain McGinniss, Nat Pryce, Steve Freeman
/** * Matcher that asserts that a JavaBean property on an argument passed to the * mock object meets the provided matcher. This is useful for when objects * are created within code under test and passed to a mock object, and you wish * to assert that the created object has certain properties. * <p/> * <h2>Example Usage</h2> * Consider the situation where we have a class representing a person, which * follows the basic JavaBean convention of having get() and possibly set() * methods for it's properties: * <pre> * public class Person { * private String name; * public Person(String person) { * this.person = person; * } * public String getName() { * return name; * } * }</pre> * * And that these person objects are generated within a piece of code under test * (a class named PersonGenerator). This object is sent to one of our mock objects * which overrides the PersonGenerationListener interface: * <pre> * public interface PersonGenerationListener { * public void personGenerated(Person person); * }</pre> * * In order to check that the code under test generates a person with name * "Iain" we would do the following: * <pre> * Mock personGenListenerMock = mock(PersonGenerationListener.class); * personGenListenerMock.expects(once()).method("personGenerated").with(and(isA(Person.class), hasProperty("Name", eq("Iain"))); * PersonGenerationListener listener = (PersonGenerationListener)personGenListenerMock.proxy();</pre> * * If an exception is thrown by the getter method for a property, the property * does not exist, is not readable, or a reflection related exception is thrown * when trying to invoke it then this is treated as an evaluation failure and * the matches method will return false. * <p/> * This matcher class will also work with JavaBean objects that have explicit * bean descriptions via an associated BeanInfo description class. See the * JavaBeans specification for more information: * <p/> * http://java.sun.com/products/javabeans/docs/index.html * * @author Iain McGinniss * @author Nat Pryce * @author Steve Freeman */
public class HasPropertyWithValue<T> extends TypeSafeDiagnosingMatcher<T> { private static final Condition.Step<PropertyDescriptor,Method> WITH_READ_METHOD = withReadMethod(); private final String propertyName; private final Matcher<Object> valueMatcher; public HasPropertyWithValue(String propertyName, Matcher<?> valueMatcher) { this.propertyName = propertyName; this.valueMatcher = nastyGenericsWorkaround(valueMatcher); } @Override public boolean matchesSafely(T bean, Description mismatch) { return propertyOn(bean, mismatch) .and(WITH_READ_METHOD) .and(withPropertyValue(bean)) .matching(valueMatcher, "property '" + propertyName + "' "); } @Override public void describeTo(Description description) { description.appendText("hasProperty(").appendValue(propertyName).appendText(", ") .appendDescriptionOf(valueMatcher).appendText(")"); } private Condition<PropertyDescriptor> propertyOn(T bean, Description mismatch) { PropertyDescriptor property = PropertyUtil.getPropertyDescriptor(propertyName, bean); if (property == null) { mismatch.appendText("No property \"" + propertyName + "\""); return notMatched(); } return matched(property, mismatch); } private Condition.Step<Method, Object> withPropertyValue(final T bean) { return new Condition.Step<Method, Object>() { @Override public Condition<Object> apply(Method readMethod, Description mismatch) { try { return matched(readMethod.invoke(bean, NO_ARGUMENTS), mismatch); } catch (Exception e) { mismatch.appendText(e.getMessage()); return notMatched(); } } }; } @SuppressWarnings("unchecked") private static Matcher<Object> nastyGenericsWorkaround(Matcher<?> valueMatcher) { return (Matcher<Object>) valueMatcher; } private static Condition.Step<PropertyDescriptor,Method> withReadMethod() { return new Condition.Step<PropertyDescriptor, java.lang.reflect.Method>() { @Override public Condition<Method> apply(PropertyDescriptor property, Description mismatch) { final Method readMethod = property.getReadMethod(); if (null == readMethod) { mismatch.appendText("property \"" + property.getName() + "\" is not readable"); return notMatched(); } return matched(readMethod, mismatch); } }; }
Creates a matcher that matches when the examined object has a JavaBean property with the specified name whose value satisfies the specified matcher.

For example:
assertThat(myBean, hasProperty("foo", equalTo("bar"))
Params:
  • propertyName – the name of the JavaBean property that examined beans should possess
  • valueMatcher – a matcher for the value of the specified property of the examined bean
/** * Creates a matcher that matches when the examined object has a JavaBean property * with the specified name whose value satisfies the specified matcher. * <p/> * For example: * <pre>assertThat(myBean, hasProperty("foo", equalTo("bar"))</pre> * * @param propertyName * the name of the JavaBean property that examined beans should possess * @param valueMatcher * a matcher for the value of the specified property of the examined bean */
@Factory public static <T> Matcher<T> hasProperty(String propertyName, Matcher<?> valueMatcher) { return new HasPropertyWithValue<T>(propertyName, valueMatcher); } }