Skip to content

Commit

Permalink
Size matchers should provide insightful debugging information
Browse files Browse the repository at this point in the history
Now, matchers like hasSize and similar, in case of mismatch,
print also the actual values contained in the collection being tested.

Closes #174
  • Loading branch information
alb-i986 committed Aug 23, 2020
1 parent 3f08267 commit 27bb8dd
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 34 deletions.
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
package org.hamcrest.collection;

import org.hamcrest.FeatureMatcher;
import org.hamcrest.Matcher;

import java.util.Arrays;

import static org.hamcrest.core.DescribedAs.describedAs;
import static org.hamcrest.core.IsEqual.equalTo;

/**
* Matches if array size satisfies a nested matcher.
*/
public class IsArrayWithSize<E> extends FeatureMatcher<E[], Integer> {
public class IsArrayWithSize<E> extends SizeMatcher<E[], E> {
public IsArrayWithSize(Matcher<? super Integer> sizeMatcher) {
super(sizeMatcher, "an array with size","array size");
super(sizeMatcher, "array");
}

@Override
protected Integer featureValueOf(E[] actual) {
return actual.length;
}

@Override
protected Iterable<? extends E> actualValues(E[] actual) {
return Arrays.asList(actual);
}

/**
* Creates a matcher for arrays that matches when the <code>length</code> of the array
* satisfies the specified matcher.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.hamcrest.collection;

import org.hamcrest.FeatureMatcher;
import org.hamcrest.Matcher;

import java.util.Collection;
Expand All @@ -10,16 +9,21 @@
/**
* Matches if collection size satisfies a nested matcher.
*/
public class IsCollectionWithSize<E> extends FeatureMatcher<Collection<? extends E>, Integer> {
public class IsCollectionWithSize<E> extends SizeMatcher<Collection<? extends E>, E> {
public IsCollectionWithSize(Matcher<? super Integer> sizeMatcher) {
super(sizeMatcher, "a collection with size", "collection size");
super(sizeMatcher, "collection");
}

@Override
protected Integer featureValueOf(Collection<? extends E> actual) {
return actual.size();
}

@Override
protected Iterable<? extends E> actualValues(Collection<? extends E> actual) {
return actual;
}

/**
* Creates a matcher for {@link java.util.Collection}s that matches when the <code>size()</code> method returns
* a value that satisfies the specified matcher.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
package org.hamcrest.collection;

import org.hamcrest.FeatureMatcher;
import org.hamcrest.Matcher;

import java.util.Iterator;

import static org.hamcrest.core.IsEqual.equalTo;

public class IsIterableWithSize<E> extends FeatureMatcher<Iterable<E>, Integer> {
public class IsIterableWithSize<E> extends SizeMatcher<Iterable<E>, E> {

public IsIterableWithSize(Matcher<? super Integer> sizeMatcher) {
super(sizeMatcher, "an iterable with size", "iterable size");
super(sizeMatcher, "iterable");
}


@Override
protected Integer featureValueOf(Iterable<E> actual) {
Expand All @@ -23,6 +21,11 @@ protected Integer featureValueOf(Iterable<E> actual) {
return size;
}

@Override
protected Iterable<? extends E> actualValues(Iterable<E> actual) {
return actual;
}

/**
* Creates a matcher for {@link Iterable}s that matches when a single pass over the
* examined {@link Iterable} yields an item count that satisfies the specified
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
package org.hamcrest.collection;

import org.hamcrest.FeatureMatcher;
import org.hamcrest.Matcher;

import java.util.Map;
import java.util.Set;

import static org.hamcrest.core.IsEqual.equalTo;

/**
* Matches if map size satisfies a nested matcher.
*/
public final class IsMapWithSize<K, V> extends FeatureMatcher<Map<? extends K, ? extends V>, Integer> {
public final class IsMapWithSize<K, V> extends SizeMatcher<Map<? extends K, ? extends V>, Map.Entry<? extends K, ? extends V>> {
@SuppressWarnings("WeakerAccess")
public IsMapWithSize(Matcher<? super Integer> sizeMatcher) {
super(sizeMatcher, "a map with size", "map size");
super(sizeMatcher, "map");
}

@Override
protected Integer featureValueOf(Map<? extends K, ? extends V> actual) {
return actual.size();
}

@Override
protected Set<? extends Map.Entry<? extends K, ? extends V>> actualValues(Map<? extends K, ? extends V> actual) {
return actual.entrySet();
}

/**
* Creates a matcher for {@link java.util.Map}s that matches when the <code>size()</code> method returns
* a value that satisfies the specified matcher.
Expand Down
50 changes: 50 additions & 0 deletions hamcrest/src/main/java/org/hamcrest/collection/SizeMatcher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.hamcrest.collection;

import org.hamcrest.Description;
import org.hamcrest.FeatureMatcher;
import org.hamcrest.Matcher;

/**
* Base class for Matchers matching a feature on a collection-like object.
* <p>
* Provides insightful debugging information in case the collection does not match,
* by printing the actual values in the mismatch description.
*
* @param <T> the type of the collection
* @param <E> the type of the elements in the collection
*/
public abstract class SizeMatcher<T, E> extends FeatureMatcher<T, Integer> {

public SizeMatcher(Matcher<? super Integer> sizeMatcher, String type) {
super(sizeMatcher, indefiniteArticle(type) + " " + type + " with size", type + " size");
}

@Override
protected abstract Integer featureValueOf(T actual);

@Override
protected boolean matchesSafely(T actual, Description mismatch) {
boolean matchesSafely = super.matchesSafely(actual, mismatch);
if (!matchesSafely) {
mismatch.appendText(". Actual values: ");
mismatch.appendValueList("[", ", ", "]", actualValues(actual));
}
return matchesSafely;
}

protected abstract Iterable<? extends E> actualValues(T actual);

private static String indefiniteArticle(String string) {
// a naive algorithm..
switch (string.charAt(0)) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
return "an";
default:
return "a";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,9 @@ public void testHasAReadableDescription() {
assertDescription("an array with size <3>", arrayWithSize(equalTo(3)));
assertDescription("an empty array", emptyArray());
}

public void testOnMismatchProvidesInsightfulDebuggingInformation() {
assertMismatchDescription("array size was <2>. Actual values: [<1>, <2>]",
arrayWithSize(equalTo(1)), new Integer[] {1, 2});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,48 +21,48 @@ protected Matcher<?> createMatcher() {

public void testMatchesWhenSizeIsCorrect() {
assertMatches("correct size", hasSize(equalTo(2)), asList(null, null));
assertMismatchDescription("collection size was <3>", hasSize(equalTo(2)), asList(null, null, null));
assertDoesNotMatch("incorrect size", hasSize(equalTo(2)), asList(null, null, null));
}

public void testMatchesCollectionWhenSizeIsCorrectUsingObjectElementType() {
Collection<Object> list = asList(null, null);
assertMatches("correct size", hasSize(equalTo(2)), list);
assertMismatchDescription("collection size was <2>", hasSize(equalTo(3)), list);
assertDoesNotMatch("incorrect size", hasSize(equalTo(3)), list);
}

public void testMatchesCollectionWhenSizeIsCorrectUsingStringElementType() {
Collection<String> list = asList("a", "b");
assertMatches("correct size", hasSize(equalTo(2)), list);
assertMismatchDescription("collection size was <2>", hasSize(equalTo(3)), list);
assertDoesNotMatch("incorrect size", hasSize(equalTo(3)), list);
}

public void testMatchesCollectionWhenSizeIsCorrectUsingWildcardElementType() {
Collection<?> list = asList("a", "b");
assertMatches("correct size", hasSize(equalTo(2)), list);
assertMismatchDescription("collection size was <2>", hasSize(equalTo(3)), list);
assertDoesNotMatch("incorrect size", hasSize(equalTo(3)), list);
}

public void testMatchesListWhenSizeIsCorrectUsingObjectElementType() {
List<Object> list = asList(null, null);
assertMatches("correct size", hasSize(equalTo(2)), list);
assertMismatchDescription("collection size was <2>", hasSize(equalTo(3)), list);
assertDoesNotMatch("incorrect size", hasSize(equalTo(3)), list);
}

public void testMatchesListWhenSizeIsCorrectUsingStringElementType() {
List<String> list = asList("a", "b");
assertMatches("correct size", hasSize(equalTo(2)), list);
assertMismatchDescription("collection size was <2>", hasSize(equalTo(3)), list);
assertDoesNotMatch("incorrect size", hasSize(equalTo(3)), list);
}

public void testMatchesListWhenSizeIsCorrectUsingWildcardElementType() {
List<?> list = asList("a", "b");
assertMatches("correct size", hasSize(equalTo(2)), list);
assertMismatchDescription("collection size was <2>", hasSize(equalTo(3)), list);
assertDoesNotMatch("incorrect size", hasSize(equalTo(3)), list);
}

public void testProvidesConvenientShortcutForHasSizeEqualTo() {
assertMatches("correct size", hasSize(2), asList(null, null));
assertMismatchDescription("collection size was <3>", hasSize(2), asList(null, null, null));
assertDoesNotMatch("incorrect size", hasSize(2), asList(null, null, null));
}

public void testHasAReadableDescription() {
Expand All @@ -74,4 +74,10 @@ public void testCompilesWithATypedCollection() {
ArrayList<String> arrayList = new ArrayList<String>();
MatcherAssert.assertThat(arrayList, hasSize(0));
}

public void testOnMismatchProvidesInsightfulDebuggingInformation() {
List<String> threeStrings = asList("a", "b", "c");
assertMismatchDescription("collection size was <3>. Actual values: [\"a\", \"b\", \"c\"]",
hasSize(equalTo(2)), threeStrings);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static org.hamcrest.collection.IsIterableWithSize.iterableWithSize;

Expand Down Expand Up @@ -34,4 +35,9 @@ public void testDoesNotMatchIncorrectSize() throws Exception {
public void testHasAReadableDescription() {
assertDescription("an iterable with size <4>", iterableWithSize(4));
}

public void testOnMismatchProvidesInsightfulDebuggingInformation() {
assertMismatchDescription("iterable size was <2>. Actual values: [<1>, <2>]",
iterableWithSize(1), Arrays.asList(1, 2));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
import org.hamcrest.MatcherAssert;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import static java.util.Arrays.asList;
import static org.hamcrest.collection.IsMapWithSize.aMapWithSize;
import static org.hamcrest.core.IsEqual.equalTo;

Expand All @@ -19,48 +22,48 @@ protected Matcher<?> createMatcher() {

public void testMatchesWhenSizeIsCorrect() {
assertMatches("correct size", aMapWithSize(equalTo(2)), mapWithKeys("a", "b"));
assertMismatchDescription("map size was <3>", aMapWithSize(equalTo(2)), mapWithKeys("a", "b", "c"));
assertDoesNotMatch("incorrect size", aMapWithSize(equalTo(2)), mapWithKeys("a", "b", "c"));
}

public void testMatchesMapWhenSizeIsCorrectUsingObjectElementType() {
Map<Object, Object> map = mapWithKeys(new Object(), new Object());
assertMatches("correct size", aMapWithSize(equalTo(2)), map);
assertMismatchDescription("map size was <2>", aMapWithSize(equalTo(3)), map);
assertDoesNotMatch("incorrect size", aMapWithSize(equalTo(3)), map);
}

public void testMatchesMapWhenSizeIsCorrectUsingStringElementType() {
Map<String, Integer> map = mapWithKeys("a", "b");
assertMatches("correct size", aMapWithSize(equalTo(2)), map);
assertMismatchDescription("map size was <2>", aMapWithSize(equalTo(3)), map);
assertDoesNotMatch("incorrect size", aMapWithSize(equalTo(3)), map);
}

public void testMatchesMapWhenSizeIsCorrectUsingWildcardElementType() {
Map<?, ?> map = mapWithKeys("a", "b");
assertMatches("correct size", aMapWithSize(equalTo(2)), map);
assertMismatchDescription("map size was <2>", aMapWithSize(equalTo(3)), map);
assertDoesNotMatch("incorrect size", aMapWithSize(equalTo(3)), map);
}

public void testMatchesListWhenSizeIsCorrectUsingObjectElementType() {
Map<Object, Object> map = mapWithKeys(new Object(), new Object());
assertMatches("correct size", aMapWithSize(equalTo(2)), map);
assertMismatchDescription("map size was <2>", aMapWithSize(equalTo(3)), map);
assertDoesNotMatch("incorrect size", aMapWithSize(equalTo(3)), map);
}

public void testMatchesListWhenSizeIsCorrectUsingStringElementType() {
Map<String, Integer> list = mapWithKeys("a", "b");
assertMatches("correct size", aMapWithSize(equalTo(2)), list);
assertMismatchDescription("map size was <2>", aMapWithSize(equalTo(3)), list);
Map<String, Integer> map = mapWithKeys("a", "b");
assertMatches("correct size", aMapWithSize(equalTo(2)), map);
assertDoesNotMatch("incorrect size", aMapWithSize(equalTo(3)), map);
}

public void testMatchesListWhenSizeIsCorrectUsingWildcardElementType() {
Map<?, ?> list = mapWithKeys("a", "b");
assertMatches("correct size", aMapWithSize(equalTo(2)), list);
assertMismatchDescription("map size was <2>", aMapWithSize(equalTo(3)), list);
Map<?, ?> map = mapWithKeys("a", "b");
assertMatches("correct size", aMapWithSize(equalTo(2)), map);
assertDoesNotMatch("incorrect size", aMapWithSize(equalTo(3)), map);
}

public void testProvidesConvenientShortcutForHasSizeEqualTo() {
assertMatches("correct size", aMapWithSize(2), mapWithKeys(new Object(), new Object()));
assertMismatchDescription("map size was <3>", aMapWithSize(2), mapWithKeys(new Object(), new Object(), new Object()));
assertDoesNotMatch("incorrect size", aMapWithSize(2), mapWithKeys(new Object(), new Object(), new Object()));
}

public void testHasAReadableDescription() {
Expand All @@ -71,12 +74,26 @@ public void testCompilesWithATypedMap() {
Map<String, Integer> arrayList = new HashMap<String, Integer>();
MatcherAssert.assertThat(arrayList, aMapWithSize(0));
}


public void testOnMismatchProvidesInsightfulDebuggingInformation() {
assertMismatchDescription("map size was <2>. Actual values: [<k1=a>, <k2=b>]",
aMapWithSize(equalTo(1)), mapWithKeysAndValues(asList("k1", "k2"), asList("a", "b")));
}

private static <K, V> Map<K, V> mapWithKeys(K... keys) {
final Map<K, V> result = new HashMap<K, V>();
for (K key : keys) {
result.put(key, null);
}
return result;
}

private static <K, V> Map<K, V> mapWithKeysAndValues(List<K> keys, List<V> values) {
final Map<K, V> result = new HashMap<K, V>();
Iterator<V> valuesIt = values.iterator();
for (K key : keys) {
result.put(key, valuesIt.next());
}
return result;
}
}

0 comments on commit 27bb8dd

Please sign in to comment.