Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Size matchers to provide insightful debugging information #294

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
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;
}
}