
org.jmockring.utils.hamcrest.CollectionIntrospectMatcher Maven / Gradle / Ivy
The newest version!
package org.jmockring.utils.hamcrest;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import org.apache.commons.beanutils.NestedNullException;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
/**
* General purpose collection matcher which allows to assert equality/expectations on named fields of the collection elements.
* The matching is not dependent on the ordering of the elements in the collection.
*
* Note: use of this matcher implicitly asserts NON NULL on the passed collection/iterable.
*
* Example:
* (with matchers (recommended)):
*
* assertThat(assets, collectionWithSize(2)
* .hasElementWhere("fileName", is("my3.jpeg")).andWhere("order", is(1))
* .hasElementWhere("fileName", is("my.jpeg")).andWhere("order", is(2))
* );
*
*
* (direct match: x.equals(y)):
*
* assertThat(assets, collectionWithSize(2)
* .hasElementWith("fileName", "my3.jpeg").andWith("order", 1)
* .hasElementWith("fileName", "my.jpeg").andWith("order", 2)
* );
*
* where assets
is a collection of Asset instances, which have fields named `fileName` and `order` (among others).
*
* @author Pavel Lechev
* @since 12/12/12
*/
public class CollectionIntrospectMatcher extends BaseMatcher {
List introspects = new ArrayList();
private int expectedSize = -1;
private boolean nullValueDetected;
private int realSize;
/**
* @param expectedSize
* @return
*/
public static CollectionIntrospectMatcher collectionWithSize(int expectedSize) {
return new CollectionIntrospectMatcher().hasSize(expectedSize);
}
/**
* Append more field expectations to already defined introspection via #hasElementWith().
* This is a chained method allowing any number of calls.
*
* @param property
* @param matcher
* @return
*/
public CollectionIntrospectMatcher andWhere(String property, Matcher matcher) {
if (this.introspects.size() == 0) {
throw new IllegalStateException("No introspects yet. Call #hasElementWith() at least once");
}
this.introspects.get(this.introspects.size() - 1).add(property, matcher);
return this;
}
/**
* Append more field expectations to already defined introspection via #hasElementWith().
* This is a chained method allowing any number of calls.
*
* @param property
* @param value
* @return
*/
public CollectionIntrospectMatcher andWith(String property, Object value) {
if (this.introspects.size() == 0) {
throw new IllegalStateException("No introspects yet. Call #hasElementWith() at least once");
}
this.introspects.get(this.introspects.size() - 1).add(property, value);
return this;
}
@Override
public void describeMismatch(Object item, Description description) {
super.describeMismatch(convertToString((Collection) item), description);
}
@Override
public void describeTo(Description description) {
if (nullValueDetected) {
description.appendText("Non-null collection");
} else if (expectedSize >= 0 && expectedSize != realSize) {
description.appendText(String.format("Size of [%s], but real size was [%s]", expectedSize, realSize));
} else {
description.appendText("\n");
for (Introspect introspect : introspects) {
description.appendValue(introspect);
description.appendText(introspect.matched ? " >> Matched \n" : " >> Unmatched \n");
if (introspect.failedValues != null) {
description.appendText("\t> Failed values: ");
for (Object failedValue : introspect.failedValues) {
description.appendText("\n \t\t > " + failedValue);
}
description.appendText("\n");
}
}
}
}
public final CollectionIntrospectMatcher hasElementWhere(String property, Matcher matcher) {
this.introspects.add(new Introspect(property, matcher));
return this;
}
/**
* Add new introspection specification.
* This must be called at least once. The #andWith() call is then used to append field expectations to this introspection.
*
* This is a chained method allowing any number of introspections to be added to the matcher.
*
* @param property
* @param value
* @return
* @see #andWith(String, Object)
*/
public final CollectionIntrospectMatcher hasElementWith(String property, Object value) {
this.introspects.add(new Introspect(property, value));
return this;
}
/**
* Set size expectations for the collection. If not called, size will not be verified.
* Can be any value equal or greater than 0.
*
* @param expectedSize
* @return
* @deprecated - use {@link #collectionWithSize(int)} instead
*/
private CollectionIntrospectMatcher hasSize(int expectedSize) {
if (expectedSize < 0) {
throw new IllegalArgumentException("Illegal value for collection size: " + expectedSize);
} else if (this.expectedSize >= 0) {
// already called
throw new IllegalStateException("Expected collection size already specified: " + expectedSize + ". Did you call #hasSize() twice?");
}
this.expectedSize = expectedSize;
return this;
}
@Override
public boolean matches(Object input) {
// some pre-checks
if (expectedSize >= 0 && expectedSize < introspects.size()) {
throw new IllegalStateException(
String.format("Expected size [%s] is less than the defined number of introspections [%s]. Must be equal(ideally) or greater.",
expectedSize,
introspects.size())
);
} else if (introspects.size() == 0) {
// no introspections defined :: do we really need to use this matcher ??
throw new IllegalStateException("No introspections defined. If only collection size is asserted, consider using the standard hamcrest matchers which provide better error feedback.");
}
// begin assertions
if (nullValueDetected = (input == null)) {
return false;
}
Collection collection = (Collection) input;
realSize = collection.size();
if (expectedSize >= 0 && realSize != expectedSize) {
return false;
}
PropertyUtilsBean propertyBean = new PropertyUtilsBean();
for (Introspect introspect : this.introspects) {
introspect.matched = false;
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy