I'd like to test that a list contains instances of an object.
For instance, with a single instance:
assertThat(mylist).containsExactly(Matchers.any(ExpectedType.class));
The array returned from tested obj
does contain exactly one object of instance ExpectedType
.
However my test fails with:
java.lang.AssertionError: Not true that <[ExpectedType@7c781c42]> contains exactly <[an instance of ExpectedType]>. It is missing <[an instance of ExpectedType]> and has unexpected items <[ExpectedType@7c781c42]>
How can I write this test?
You're trying to write a test to see if a List
contains exactly one instance of a particular class using Hamcrest and Truth. Instead, you should be writing this test with either Hamcrest or Truth. Hamcrest and Truth are both libraries for making tests more expressive, each with their own particular usage, style, and syntax. You can use them alongside each other in your tests if you like, but chaining their methods together as you are doing is not going to work. (Maybe you got confused because both libraries can have assertions that start with assertThat
?) So for this particular test, you need to pick one of them and go with it.
Both libraries, however, are lacking the built-in functionality of checking that a List
has one and only one item that satisfies a condition. So with either library, you have two options: either you can do a little bit of pre-processing on the list so that you can use a built-in assertion, or you can extend the language of the library to give it this functionality.
The following is an example class that demonstrates both options for both libraries:
import com.google.common.collect.FluentIterable;
import com.google.common.truth.*;
import org.hamcrest.*;
import org.junit.Test;
import java.util.*;
import static com.google.common.truth.Truth.assertAbout;
import static com.google.common.truth.Truth.assert_;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
public class ExactlyOneInstanceTest {
List<Object> myList = Arrays.asList("", 3, 'A', new Object());
@Test
public void hamcrestBuiltInTestExactlyOneInstance() {
long theNumberOfStringsInMyList = myList.stream().filter(o -> o instanceof String).count();
assertThat(theNumberOfStringsInMyList, equalTo(1L));
}
@Test
public void hamcrestExtendedTestExactlyOneInstance() {
assertThat(myList, HasExactlyOne.itemThat(is(instanceOf(String.class))));
}
@Test
public void truthBuiltInTestExactlyOneInstance() {
long theNumberOfStringsInMyList = myList.stream().filter(o -> o instanceof String).count();
// can't static import Truth.assertThat because of name clash,
// but we can use this alternative form
assert_().that(theNumberOfStringsInMyList).isEqualTo(1);
}
@Test
public void truthExtendedTestExactlyOneInstance() {
assertAbout(iterable()).that(myList).containsExactlyOneInstanceOf(String.class);
}
// Hamcrest custom matcher
static class HasExactlyOne<T> extends TypeSafeDiagnosingMatcher<Iterable<? super T>> {
Matcher<? super T> elementMatcher;
HasExactlyOne(Matcher<? super T> elementMatcher) {
this.elementMatcher = elementMatcher;
}
@Factory
public static <T> Matcher<Iterable<? super T>> itemThat(Matcher<? super T> itemMatcher) {
return new HasExactlyOne<>(itemMatcher);
}
@Override
public void describeTo(Description description) {
description
.appendText("a collection containing exactly one item that ")
.appendDescriptionOf(elementMatcher);
}
@Override
protected boolean matchesSafely(Iterable<? super T> item, Description mismatchDescription) {
return FluentIterable.from(item).filter(o -> elementMatcher.matches(o)).size() == 1;
}
}
// Truth custom extension
static <T> SubjectFactory<ExtendedIterableSubject<T>, Iterable<T>> iterable() {
return new SubjectFactory<ExtendedIterableSubject<T>, Iterable<T>>() {
@Override
public ExtendedIterableSubject<T> getSubject(FailureStrategy fs, Iterable<T> target) {
return new ExtendedIterableSubject<>(fs, target);
}
};
}
static class ExtendedIterableSubject<T> extends IterableSubject<ExtendedIterableSubject<T>, T, Iterable<T>> {
ExtendedIterableSubject(FailureStrategy failureStrategy, Iterable<T> list) {
super(failureStrategy, list);
}
void containsExactlyOneInstanceOf(Class<?> clazz) {
if (FluentIterable.from(getSubject()).filter(clazz).size() != 1) {
fail("contains exactly one instance of", clazz.getName());
}
}
}
}
Try running and looking over that class and use whichever way seems most natural for you. When writing future tests, just try sticking with the built-in assertions available to you and try to make the intent of the @Test
methods and their assertions instantly readable. If you see that you are writing the same code multiple times, or that a test method is not so simple to read, then refactor and/or extend the language of the library you're using. Repeat until everything is tested and all tests are easily understandable. Enjoy!