Skip to content

Commit

Permalink
Allow matching against polymorphic collections
Browse files Browse the repository at this point in the history
  • Loading branch information
tumbarumba committed Sep 8, 2024
1 parent d11ad94 commit 216b475
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 42 deletions.
8 changes: 4 additions & 4 deletions hamcrest/src/main/java/org/hamcrest/CoreMatchers.java
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ public static org.hamcrest.Matcher<java.lang.Object> anything(java.lang.String d
* the matcher to apply to items provided by the examined {@link Iterable}
* @return The matcher.
*/
public static <T> org.hamcrest.Matcher<java.lang.Iterable<? super T>> hasItem(org.hamcrest.Matcher<? super T> itemMatcher) {
public static <T> org.hamcrest.Matcher<java.lang.Iterable<? extends T>> hasItem(org.hamcrest.Matcher<? super T> itemMatcher) {
return IsIterableContaining.hasItem(itemMatcher);
}

Expand All @@ -249,7 +249,7 @@ public static <T> org.hamcrest.Matcher<java.lang.Iterable<? super T>> hasItem(or
* the item to compare against the items provided by the examined {@link Iterable}
* @return The matcher.
*/
public static <T> org.hamcrest.Matcher<java.lang.Iterable<? super T>> hasItem(T item) {
public static <T> org.hamcrest.Matcher<java.lang.Iterable<? extends T>> hasItem(T item) {
return IsIterableContaining.hasItem(item);
}

Expand All @@ -268,7 +268,7 @@ public static <T> org.hamcrest.Matcher<java.lang.Iterable<? super T>> hasItem(T
* @return The matcher.
*/
@SafeVarargs
public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItems(org.hamcrest.Matcher<? super T>... itemMatchers) {
public static <T> org.hamcrest.Matcher<java.lang.Iterable<? extends T>> hasItems(org.hamcrest.Matcher<? super T>... itemMatchers) {
return IsIterableContaining.hasItems(itemMatchers);
}

Expand All @@ -287,7 +287,7 @@ public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItems(org.hamcr
* @return The matcher.
*/
@SafeVarargs
public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItems(T... items) {
public static <T> org.hamcrest.Matcher<java.lang.Iterable<? extends T>> hasItems(T... items) {
return IsIterableContaining.hasItems(items);
}

Expand Down
8 changes: 4 additions & 4 deletions hamcrest/src/main/java/org/hamcrest/Matchers.java
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ public static org.hamcrest.Matcher<java.lang.Object> anything(java.lang.String d
* the matcher to apply to items provided by the examined {@link Iterable}
* @return The matcher.
*/
public static <T> org.hamcrest.Matcher<java.lang.Iterable<? super T>> hasItem(org.hamcrest.Matcher<? super T> itemMatcher) {
public static <T> org.hamcrest.Matcher<java.lang.Iterable<? extends T>> hasItem(org.hamcrest.Matcher<? super T> itemMatcher) {
return IsIterableContaining.hasItem(itemMatcher);
}

Expand All @@ -467,7 +467,7 @@ public static <T> org.hamcrest.Matcher<java.lang.Iterable<? super T>> hasItem(or
* the item to compare against the items provided by the examined {@link Iterable}
* @return The matcher.
*/
public static <T> org.hamcrest.Matcher<java.lang.Iterable<? super T>> hasItem(T item) {
public static <T> org.hamcrest.Matcher<java.lang.Iterable<? extends T>> hasItem(T item) {
return IsIterableContaining.hasItem(item);
}

Expand All @@ -486,7 +486,7 @@ public static <T> org.hamcrest.Matcher<java.lang.Iterable<? super T>> hasItem(T
* @return The matcher.
*/
@SafeVarargs
public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItems(org.hamcrest.Matcher<? super T>... itemMatchers) {
public static <T> org.hamcrest.Matcher<java.lang.Iterable<? extends T>> hasItems(org.hamcrest.Matcher<? super T>... itemMatchers) {
return IsIterableContaining.hasItems(itemMatchers);
}

Expand All @@ -505,7 +505,7 @@ public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItems(org.hamcr
* @return The matcher.
*/
@SafeVarargs
public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItems(T... items) {
public static <T> org.hamcrest.Matcher<java.lang.Iterable<? extends T>> hasItems(T... items) {
return IsIterableContaining.hasItems(items);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
public class HasItemInArray<T> extends TypeSafeMatcher<T[]> {

private final Matcher<? super T> elementMatcher;
private final TypeSafeDiagnosingMatcher<Iterable<? super T>> collectionMatcher;
private final TypeSafeDiagnosingMatcher<Iterable<? extends T>> collectionMatcher;

/**
* Constructor, best called from {@link ArrayMatching}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* @deprecated As of release 2.1, replaced by {@link IsIterableContaining}.
*/
@Deprecated
public class IsCollectionContaining<T> extends TypeSafeDiagnosingMatcher<Iterable<? super T>> {
public class IsCollectionContaining<T> extends TypeSafeDiagnosingMatcher<Iterable<? extends T>> {

private final IsIterableContaining<T> delegate;

Expand All @@ -26,7 +26,7 @@ public IsCollectionContaining(Matcher<? super T> elementMatcher) {
}

@Override
protected boolean matchesSafely(Iterable<? super T> collection, Description mismatchDescription) {
protected boolean matchesSafely(Iterable<? extends T> collection, Description mismatchDescription) {
return delegate.matchesSafely(collection, mismatchDescription);
}

Expand All @@ -51,7 +51,7 @@ public void describeTo(Description description) {
* the matcher to apply to items provided by the examined {@link Iterable}
* @return The matcher.
*/
public static <T> Matcher<Iterable<? super T>> hasItem(Matcher<? super T> itemMatcher) {
public static <T> Matcher<Iterable<? extends T>> hasItem(Matcher<? super T> itemMatcher) {
return IsIterableContaining.hasItem(itemMatcher);
}

Expand All @@ -70,7 +70,7 @@ public static <T> Matcher<Iterable<? super T>> hasItem(Matcher<? super T> itemMa
* the item to compare against the items provided by the examined {@link Iterable}
* @return The matcher.
*/
public static <T> Matcher<Iterable<? super T>> hasItem(T item) {
public static <T> Matcher<Iterable<? extends T>> hasItem(T item) {
// Doesn't forward to hasItem() method so compiler can sort out generics.
return IsIterableContaining.hasItem(item);
}
Expand All @@ -91,7 +91,7 @@ public static <T> Matcher<Iterable<? super T>> hasItem(T item) {
* @return The matcher.
*/
@SafeVarargs
public static <T> Matcher<Iterable<T>> hasItems(Matcher<? super T>... itemMatchers) {
public static <T> Matcher<Iterable<? extends T>> hasItems(Matcher<? super T>... itemMatchers) {
return IsIterableContaining.hasItems(itemMatchers);
}

Expand All @@ -111,7 +111,7 @@ public static <T> Matcher<Iterable<T>> hasItems(Matcher<? super T>... itemMatche
* @return The matcher.
*/
@SafeVarargs
public static <T> Matcher<Iterable<T>> hasItems(T... items) {
public static <T> Matcher<Iterable<? extends T>> hasItems(T... items) {
return IsIterableContaining.hasItems(items);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* Tests if an iterable contains matching elements.
* @param <T> the type of items in the iterable
*/
public class IsIterableContaining<T> extends TypeSafeDiagnosingMatcher<Iterable<? super T>> {
public class IsIterableContaining<T> extends TypeSafeDiagnosingMatcher<Iterable<? extends T>> {

private final Matcher<? super T> elementMatcher;

Expand All @@ -31,7 +31,7 @@ public IsIterableContaining(Matcher<? super T> elementMatcher) {
}

@Override
protected boolean matchesSafely(Iterable<? super T> collection, Description mismatchDescription) {
protected boolean matchesSafely(Iterable<? extends T> collection, Description mismatchDescription) {
if (isEmpty(collection)) {
mismatchDescription.appendText("was empty");
return false;
Expand All @@ -56,7 +56,7 @@ protected boolean matchesSafely(Iterable<? super T> collection, Description mism
return false;
}

private boolean isEmpty(Iterable<? super T> iterable) {
private boolean isEmpty(Iterable<? extends T> iterable) {
return ! iterable.iterator().hasNext();
}

Expand All @@ -81,7 +81,7 @@ public void describeTo(Description description) {
* the matcher to apply to items provided by the examined {@link Iterable}
* @return The matcher.
*/
public static <T> Matcher<Iterable<? super T>> hasItem(Matcher<? super T> itemMatcher) {
public static <T> Matcher<Iterable<? extends T>> hasItem(Matcher<? super T> itemMatcher) {
return new IsIterableContaining<>(itemMatcher);
}

Expand All @@ -99,7 +99,7 @@ public static <T> Matcher<Iterable<? super T>> hasItem(Matcher<? super T> itemMa
* the item to compare against the items provided by the examined {@link Iterable}
* @return The matcher.
*/
public static <T> Matcher<Iterable<? super T>> hasItem(T item) {
public static <T> Matcher<Iterable<? extends T>> hasItem(T item) {
// Doesn't forward to hasItem() method so compiler can sort out generics.
return new IsIterableContaining<>(equalTo(item));
}
Expand All @@ -119,8 +119,8 @@ public static <T> Matcher<Iterable<? super T>> hasItem(T item) {
* @return The matcher.
*/
@SafeVarargs
public static <T> Matcher<Iterable<T>> hasItems(Matcher<? super T>... itemMatchers) {
List<Matcher<? super Iterable<T>>> all = new ArrayList<>(itemMatchers.length);
public static <T> Matcher<Iterable<? extends T>> hasItems(Matcher<? super T>... itemMatchers) {
List<Matcher<? super Iterable<? extends T>>> all = new ArrayList<>(itemMatchers.length);

for (Matcher<? super T> elementMatcher : itemMatchers) {
// Doesn't forward to hasItem() method so compiler can sort out generics.
Expand All @@ -145,8 +145,8 @@ public static <T> Matcher<Iterable<T>> hasItems(Matcher<? super T>... itemMatche
* @return The matcher.
*/
@SafeVarargs
public static <T> Matcher<Iterable<T>> hasItems(T... items) {
List<Matcher<? super Iterable<T>>> all = new ArrayList<>(items.length);
public static <T> Matcher<Iterable<? extends T>> hasItems(T... items) {
List<Matcher<? super Iterable<? extends T>>> all = new ArrayList<>(items.length);
for (T item : items) {
all.add(hasItem(item));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,17 @@ protected Matcher<?> createMatcher() {
}

public void testMatchesACollectionThatContainsAnElementMatchingTheGivenMatcher() {
Matcher<Iterable<? super String>> itemMatcher = hasItem(equalTo("a"));
Matcher<Iterable<? extends String>> itemMatcher = hasItem(equalTo("a"));

assertMatches("should match list that contains 'a'",
itemMatcher, asList("a", "b", "c"));
}

public void testDoesNotMatchCollectionThatDoesntContainAnElementMatchingTheGivenMatcher() {
final Matcher<Iterable<? super String>> matcher1 = hasItem(mismatchable("a"));
final Matcher<Iterable<? extends String>> matcher1 = hasItem(mismatchable("a"));
assertMismatchDescription("mismatches were: [mismatched: b, mismatched: c]", matcher1, asList("b", "c"));

final Matcher<Iterable<? super String>> matcher2 = hasItem(equalTo("a"));
final Matcher<Iterable<? extends String>> matcher2 = hasItem(equalTo("a"));
assertMismatchDescription("was empty", matcher2, new ArrayList<String>());
}

Expand All @@ -55,27 +55,27 @@ public void testCanMatchItemWhenCollectionHoldsSuperclass() // Issue 24

@SuppressWarnings("unchecked")
public void testMatchesAllItemsInCollection() {
final Matcher<Iterable<String>> matcher1 = hasItems(equalTo("a"), equalTo("b"), equalTo("c"));
final Matcher<Iterable<? extends String>> matcher1 = hasItems(equalTo("a"), equalTo("b"), equalTo("c"));
assertMatches("should match list containing all items",
matcher1,
asList("a", "b", "c"));

final Matcher<Iterable<String>> matcher2 = hasItems("a", "b", "c");
final Matcher<Iterable<? extends String>> matcher2 = hasItems("a", "b", "c");
assertMatches("should match list containing all items (without matchers)",
matcher2,
asList("a", "b", "c"));

final Matcher<Iterable<String>> matcher3 = hasItems(equalTo("a"), equalTo("b"), equalTo("c"));
final Matcher<Iterable<? extends String>> matcher3 = hasItems(equalTo("a"), equalTo("b"), equalTo("c"));
assertMatches("should match list containing all items in any order",
matcher3,
asList("c", "b", "a"));

final Matcher<Iterable<String>> matcher4 = hasItems(equalTo("a"), equalTo("b"), equalTo("c"));
final Matcher<Iterable<? extends String>> matcher4 = hasItems(equalTo("a"), equalTo("b"), equalTo("c"));
assertMatches("should match list containing all items plus others",
matcher4,
asList("e", "c", "b", "a", "d"));

final Matcher<Iterable<String>> matcher5 = hasItems(equalTo("a"), equalTo("b"), equalTo("c"));
final Matcher<Iterable<? extends String>> matcher5 = hasItems(equalTo("a"), equalTo("b"), equalTo("c"));
assertDoesNotMatch("should not match list unless it contains all items",
matcher5,
asList("e", "c", "b", "d")); // 'a' missing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@
import org.junit.Test;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import static java.util.Arrays.asList;
import static java.util.Collections.singleton;
import static org.hamcrest.AbstractMatcherTest.*;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.hamcrest.core.IsIterableContaining.hasItem;
import static org.hamcrest.core.IsIterableContaining.hasItems;
import static org.hamcrest.core.IsEqual.equalTo;

public final class IsIterableContainingTest {

Expand All @@ -27,14 +30,14 @@ public final class IsIterableContainingTest {

@Test public void
matchesACollectionThatContainsAnElementForTheGivenMatcher() {
final Matcher<Iterable<? super String>> itemMatcher = hasItem(equalTo("a"));
final Matcher<Iterable<? extends String>> itemMatcher = hasItem(equalTo("a"));

assertMatches("list containing 'a'", itemMatcher, asList("a", "b", "c"));
}

@Test public void
doesNotMatchCollectionWithoutAnElementForGivenMatcher() {
final Matcher<Iterable<? super String>> matcher = hasItem(mismatchable("a"));
final Matcher<Iterable<? extends String>> matcher = hasItem(mismatchable("a"));

assertMismatchDescription("mismatches were: [mismatched: b, mismatched: c]", matcher, asList("b", "c"));
assertMismatchDescription("was empty", matcher, new ArrayList<String>());
Expand Down Expand Up @@ -62,31 +65,31 @@ public final class IsIterableContainingTest {
@SuppressWarnings("unchecked")
@Test public void
matchesMultipleItemsInCollection() {
final Matcher<Iterable<String>> matcher1 = hasItems(equalTo("a"), equalTo("b"), equalTo("c"));
final Matcher<Iterable<? extends String>> matcher1 = hasItems(equalTo("a"), equalTo("b"), equalTo("c"));
assertMatches("list containing all items", matcher1, asList("a", "b", "c"));

final Matcher<Iterable<String>> matcher2 = hasItems("a", "b", "c");
final Matcher<Iterable<? extends String>> matcher2 = hasItems("a", "b", "c");
assertMatches("list containing all items (without matchers)", matcher2, asList("a", "b", "c"));

final Matcher<Iterable<String>> matcher3 = hasItems(equalTo("a"), equalTo("b"), equalTo("c"));
final Matcher<Iterable<? extends String>> matcher3 = hasItems(equalTo("a"), equalTo("b"), equalTo("c"));
assertMatches("list containing all items in any order", matcher3, asList("c", "b", "a"));

final Matcher<Iterable<String>> matcher4 = hasItems(equalTo("a"), equalTo("b"), equalTo("c"));
final Matcher<Iterable<? extends String>> matcher4 = hasItems(equalTo("a"), equalTo("b"), equalTo("c"));
assertMatches("list containing all items plus others", matcher4, asList("e", "c", "b", "a", "d"));

final Matcher<Iterable<String>> matcher5 = hasItems(equalTo("a"), equalTo("b"), equalTo("c"));
final Matcher<Iterable<? extends String>> matcher5 = hasItems(equalTo("a"), equalTo("b"), equalTo("c"));
assertDoesNotMatch("not match list unless it contains all items", matcher5, asList("e", "c", "b", "d")); // 'a' missing
}

@Test public void
reportsMismatchWithAReadableDescriptionForMultipleItems() {
final Matcher<Iterable<Integer>> matcher = hasItems(3, 4);
final Matcher<Iterable<? extends Integer>> matcher = hasItems(3, 4);

assertMismatchDescription("a collection containing <4> mismatches were: [was <1>, was <2>, was <3>]",
matcher, asList(1, 2, 3));
}

private static Matcher<? super String> mismatchable(final String string) {
private static Matcher<String> mismatchable(final String string) {
return new TypeSafeDiagnosingMatcher<String>() {
@Override
protected boolean matchesSafely(String item, Description mismatchDescription) {
Expand All @@ -104,4 +107,67 @@ public void describeTo(Description description) {
};
}

@Test public void
matchesPolymorphicTypes() {
Collection<Dog> dogs = singleton(new Dog("Spot"));
Animal spotAsAnimal = new Dog("Spot");
assertMatches(hasItem(spotAsAnimal), dogs);
Dog spotAsDog = new Dog("Spot");
assertMatches(hasItem(spotAsDog), dogs);

Collection<? extends Animal> animals = asList(
new Dog("Fido"), new Cat("Whiskers"));
Dog fido = new Dog("Fido");
Matcher<Iterable<? extends Animal>> dogsMatcher = hasItem(fido);
assertMatches(dogsMatcher, animals);

Cat whiskers = new Cat("Whiskers");
assertMatches(hasItem(whiskers), animals);

Matcher<Iterable<? extends Animal>> iterableMatcher = hasItems(fido, whiskers);
assertMatches(iterableMatcher, animals);
assertMatches(not(hasItem(spotAsAnimal)), animals);
}

abstract static class Animal {
private final String name;

Animal(String name) {
this.name = name;
}

public String name() {
return this.name;
}

@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}

if (getClass() != obj.getClass()) {
return false;
}

return name.equals(((Animal) obj).name);
}

@Override
public int hashCode() {
return name.hashCode();
}
}

static class Dog extends Animal {
public Dog(String name) {
super(name);
}
}

static class Cat extends Animal {
public Cat(String name) {
super(name);
}
}
}

0 comments on commit 216b475

Please sign in to comment.