Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions hamcrest/src/main/java/org/hamcrest/FeatureMatcher.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.hamcrest;

import java.util.function.Function;

import org.hamcrest.internal.ReflectiveTypeFinder;

/**
Expand Down Expand Up @@ -29,6 +31,20 @@ public FeatureMatcher(Matcher<? super U> subMatcher, String featureDescription,
this.featureName = featureName;
}

/**
* Constructor
* @param subMatcher The matcher to apply to the feature
* @param featureDescription Descriptive text to use in describeTo
* @param featureName Identifying text for mismatch message
* @param expectedType expected type of the feature value
*/
private FeatureMatcher(Matcher<? super U> subMatcher, String featureDescription, String featureName, Class<?> expectedType) {
super(expectedType);
this.subMatcher = subMatcher;
this.featureDescription = featureDescription;
this.featureName = featureName;
}

/**
* Implement this to extract the interesting feature.
* @param actual the target object
Expand All @@ -53,4 +69,20 @@ public final void describeTo(Description description) {
.appendDescriptionOf(subMatcher);
}

/**
* Create a matcher that matches a feature of an object.
*
* @param expected the matcher for the expected feature value
* @param extractor function to extract the feature from the object
* @param featureDescription descriptive text to use in describeTo
* @param featureName identifying text for mismatch message
* @param expectedType expected type to match against
* @return a matcher that matches the feature of the object
*/
public static <T,F> Matcher<T> matcher(final Matcher<F> expected, final Function<T, F> extractor, String featureDescription, String featureName, Class<T> expectedType) {
return new FeatureMatcher<T, F>(expected, featureDescription, featureName, expectedType) {
@Override protected F featureValueOf(T actual) { return extractor.apply(actual); }
};
}

}
29 changes: 29 additions & 0 deletions hamcrest/src/main/java/org/hamcrest/TypeSafeDiagnosingMatcher.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.hamcrest;

import java.util.function.Predicate;

import org.hamcrest.internal.ReflectiveTypeFinder;

/**
Expand Down Expand Up @@ -82,4 +84,31 @@ public final void describeMismatch(Object item, Description mismatchDescription)
}
}

/**
* Creates a TypeSafeDiagnosingMatcher that matches an item based on a predicate.
*
* @param <T> Type of the item to match
* @param predicate Predicate to test the item
* @param successDescription Description to use when the predicate matches
* @param failureDescription Description to use when the predicate does not match
* @param expectedType Expected type of the item to match
* @return Matcher that matches the item based on the predicate
*/
public static <T> Matcher<T> matcher(Predicate<T> predicate, final String successDescription, final String failureDescription, Class<T> expectedType) {
return new TypeSafeDiagnosingMatcher<T>(expectedType) {
public boolean matchesSafely(T actual, Description mismatchDescription) {
final boolean result = predicate.test(actual);
if (!result) {
mismatchDescription.appendText(String.format("'%s' %s", actual, failureDescription));
}
return result;
}

public void describeTo(Description description) {
description.appendText(successDescription);
}
};
}


}
26 changes: 26 additions & 0 deletions hamcrest/src/test/java/org/hamcrest/FeatureMatcherTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,41 @@
public final class FeatureMatcherTest {

private final FeatureMatcher<Thingy, String> resultMatcher = resultMatcher();
private final Matcher<Thingy> resultMatcherStaticCtr =
FeatureMatcher.matcher(new Match("bar"), t->t.getResult(), "Thingy with result", "result", Thingy.class);

@Test public void
matchesPartOfAnObject() {
assertMatches("feature", resultMatcher, new Thingy("bar"));
assertDescription("Thingy with result \"bar\"", resultMatcher);
}

@Test public void
matchesPartOfAnObject_staticConstructor() {
assertMatches("feature", resultMatcherStaticCtr, new Thingy("bar"));
assertDescription("Thingy with result \"bar\"", resultMatcherStaticCtr);
}

@Test public void
mismatchesPartOfAnObject() {
assertMismatchDescription("result mismatch-description", resultMatcher, new Thingy("foo"));
}

@Test public void
mismatchesPartOfAnObject_staticConstructor() {
assertMismatchDescription("result mismatch-description", resultMatcherStaticCtr, new Thingy("foo"));
}

@Test public void
doesNotThrowNullPointerException() {
assertMismatchDescription("was null", resultMatcher, null);
}

@Test public void
doesNotThrowNullPointerException_staticConstructor() {
assertMismatchDescription("was null", resultMatcherStaticCtr, null);
}

@Test public void
doesNotThrowClassCastException() {
resultMatcher.matches(new ShouldNotMatch());
Expand All @@ -34,6 +52,14 @@ public final class FeatureMatcherTest {
assertEquals("was ShouldNotMatch <ShouldNotMatch>", mismatchDescription.toString());
}

@Test public void
doesNotThrowClassCastException_staticConstructor() {
resultMatcherStaticCtr.matches(new ShouldNotMatch());
StringDescription mismatchDescription = new StringDescription();
resultMatcherStaticCtr.describeMismatch(new ShouldNotMatch(), mismatchDescription);
assertEquals("was ShouldNotMatch <ShouldNotMatch>", mismatchDescription.toString());
}

public static class Match extends IsEqual<String> {
public Match(String equalArg) { super(equalArg); }
@Override public void describeMismatch(Object item, Description description) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ public class TypeSafeDiagnosingMatcherTest {
assertMismatchDescription("mismatching", STRING_MATCHER, "other");
}

@Test public void
describesMismatches_staticConstructor() {
Matcher<String> stringMatcher =
TypeSafeDiagnosingMatcher.matcher(item->false,"matches","mismatching",String.class);
assertMismatchDescription("was null", STRING_MATCHER, null);
assertMismatchDescription("was Character \"c\"", STRING_MATCHER, 'c');
assertMismatchDescription("mismatching", STRING_MATCHER, "other");
}

@Test public void
detects_non_builtin_types() {
final Matcher<NotBuiltIn> matcher = new TypeSafeDiagnosingMatcher<NotBuiltIn>() {
Expand All @@ -32,13 +41,30 @@ protected boolean matchesSafely(NotBuiltIn item, Description mismatchDescription
assertDoesNotMatch("other not built in", (Matcher)matcher, new OtherNotBuiltIn());
}

@Test public void
detects_non_builtin_types_static_constructor() {
final Matcher<NotBuiltIn> matcher =
TypeSafeDiagnosingMatcher.matcher(item->true,"a builtin","a not builtin",NotBuiltIn.class);

assertMatches("not built in", matcher, new NotBuiltIn());
assertDoesNotMatch("other not built in", (Matcher)matcher, new OtherNotBuiltIn());
}

@Test public void
filters_type_for_subclassed_matcher_when_expected_type_passed_in() {
final Matcher<NotBuiltIn> matcher = new SubMatcher<>(new NotBuiltIn());

assertMatches("not built in", matcher, new NotBuiltIn());
assertDoesNotMatch("other not built in", (Matcher)matcher, new OtherNotBuiltIn());
}
}

@Test public void
filters_type_for_subclassed_matcher_when_expected_type_passed_in_staticConstructor() {
final Matcher<NotBuiltIn> matcher = SubMatcher.matcher(new NotBuiltIn());

assertMatches("not built in", matcher, new NotBuiltIn());
assertDoesNotMatch("other not built in", (Matcher)matcher, new OtherNotBuiltIn());
}

@Test public void
but_cannot_detect_generic_type_in_subclassed_matcher_using_reflection() {
Expand All @@ -59,6 +85,7 @@ protected boolean matchesSafely(String item, Description mismatchDescription) {
public void describeTo(Description description) { }
};


public static class SubMatcher<T> extends TypeSafeDiagnosingMatcher<T> {
public SubMatcher() {
super();
Expand All @@ -68,6 +95,12 @@ public SubMatcher(T expectedObject) {
}
@Override protected boolean matchesSafely(T item, Description mismatchDescription) { return true; }
@Override public void describeTo(Description description) { description.appendText("sub type"); }

static <T> Matcher<T> matcher(T expectedObject) {
return new SubMatcher<T>(expectedObject) {

};
}
}

public static class NotBuiltIn {
Expand Down