diff --git a/hamcrest/src/main/java/org/hamcrest/FeatureMatcher.java b/hamcrest/src/main/java/org/hamcrest/FeatureMatcher.java index 5c7511ce..e7c5439d 100644 --- a/hamcrest/src/main/java/org/hamcrest/FeatureMatcher.java +++ b/hamcrest/src/main/java/org/hamcrest/FeatureMatcher.java @@ -1,5 +1,7 @@ package org.hamcrest; +import java.util.function.Function; + import org.hamcrest.internal.ReflectiveTypeFinder; /** @@ -29,6 +31,20 @@ public FeatureMatcher(Matcher 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 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 @@ -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 Matcher matcher(final Matcher expected, final Function extractor, String featureDescription, String featureName, Class expectedType) { + return new FeatureMatcher(expected, featureDescription, featureName, expectedType) { + @Override protected F featureValueOf(T actual) { return extractor.apply(actual); } + }; + } + } diff --git a/hamcrest/src/main/java/org/hamcrest/TypeSafeDiagnosingMatcher.java b/hamcrest/src/main/java/org/hamcrest/TypeSafeDiagnosingMatcher.java index 4bcf871b..0ad76c4c 100644 --- a/hamcrest/src/main/java/org/hamcrest/TypeSafeDiagnosingMatcher.java +++ b/hamcrest/src/main/java/org/hamcrest/TypeSafeDiagnosingMatcher.java @@ -1,5 +1,7 @@ package org.hamcrest; +import java.util.function.Predicate; + import org.hamcrest.internal.ReflectiveTypeFinder; /** @@ -82,4 +84,31 @@ public final void describeMismatch(Object item, Description mismatchDescription) } } + /** + * Creates a TypeSafeDiagnosingMatcher that matches an item based on a predicate. + * + * @param 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 Matcher matcher(Predicate predicate, final String successDescription, final String failureDescription, Class expectedType) { + return new TypeSafeDiagnosingMatcher(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); + } + }; + } + + } diff --git a/hamcrest/src/test/java/org/hamcrest/FeatureMatcherTest.java b/hamcrest/src/test/java/org/hamcrest/FeatureMatcherTest.java index 82e89aa2..ee020957 100644 --- a/hamcrest/src/test/java/org/hamcrest/FeatureMatcherTest.java +++ b/hamcrest/src/test/java/org/hamcrest/FeatureMatcherTest.java @@ -9,6 +9,8 @@ public final class FeatureMatcherTest { private final FeatureMatcher resultMatcher = resultMatcher(); + private final Matcher resultMatcherStaticCtr = + FeatureMatcher.matcher(new Match("bar"), t->t.getResult(), "Thingy with result", "result", Thingy.class); @Test public void matchesPartOfAnObject() { @@ -16,16 +18,32 @@ public final class FeatureMatcherTest { 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()); @@ -34,6 +52,14 @@ public final class FeatureMatcherTest { assertEquals("was ShouldNotMatch ", mismatchDescription.toString()); } + @Test public void + doesNotThrowClassCastException_staticConstructor() { + resultMatcherStaticCtr.matches(new ShouldNotMatch()); + StringDescription mismatchDescription = new StringDescription(); + resultMatcherStaticCtr.describeMismatch(new ShouldNotMatch(), mismatchDescription); + assertEquals("was ShouldNotMatch ", mismatchDescription.toString()); + } + public static class Match extends IsEqual { public Match(String equalArg) { super(equalArg); } @Override public void describeMismatch(Object item, Description description) { diff --git a/hamcrest/src/test/java/org/hamcrest/TypeSafeDiagnosingMatcherTest.java b/hamcrest/src/test/java/org/hamcrest/TypeSafeDiagnosingMatcherTest.java index 61062074..c6df85f1 100644 --- a/hamcrest/src/test/java/org/hamcrest/TypeSafeDiagnosingMatcherTest.java +++ b/hamcrest/src/test/java/org/hamcrest/TypeSafeDiagnosingMatcherTest.java @@ -17,6 +17,15 @@ public class TypeSafeDiagnosingMatcherTest { assertMismatchDescription("mismatching", STRING_MATCHER, "other"); } + @Test public void + describesMismatches_staticConstructor() { + Matcher 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 matcher = new TypeSafeDiagnosingMatcher() { @@ -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 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 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 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() { @@ -59,6 +85,7 @@ protected boolean matchesSafely(String item, Description mismatchDescription) { public void describeTo(Description description) { } }; + public static class SubMatcher extends TypeSafeDiagnosingMatcher { public SubMatcher() { super(); @@ -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 Matcher matcher(T expectedObject) { + return new SubMatcher(expectedObject) { + + }; + } } public static class NotBuiltIn {