First, note that IterableSubject.containsExactly()
asserts that the input "contains exactly the provided objects or fails." This means - even if you could pass Matcher
objects here - that we're asserting the list contains exactly one ExpectedType
instance. Neither of the existing answers correctly enforce that invariant (instead heenenee's method asserts one instance of ExpectedType
and any number of other instances, and your solution asserts that the list contains any number of instances of ExpectedType
). As I read your question you do intend to assert the exactly-one property, but regardless this demonstrates a problem with the accepted solution - it can accidentally lead to assertions you didn't intend to make.
When I run into limitations of the Truth API like this, the first thing I always try is simply splitting the assertion up into separate steps. This often proves to be easy to write, easy to read, and generally error-proof. Understandably, people often try to look for elegant one-liners with Truth, but generally speaking there's nothing wrong with making sequential assertions.
It's hard to beat that strategy here:
assertThat(ls).hasSize(1);
assertThat(Iterables.getOnlyElement(ls)).isInstanceOf(String.class);
If the iterable isn't of size 1 we'll get an error telling us that (along with the contents of the iterable). If it is, we assert that the only element is an instance of String
. Done!
For the general case of n instances the code does admittedly get a little messier, but it's still reasonable. We just use assertWithMessage()
to include additional context about the list in the isInstanceOf()
assertions:
assertThat(ls).hasSize(n);
for (int i = 0; i < ls.size(); i++) {
assertWithMessage("list: %s - index: %s", ls, i)
.that(ls.get(i)).isInstanceOf(String.class);
}
This is both more readable and much more clearly correct than implementing your own custom Subject
.
As of Truth 0.29 you can do better using "Fuzzy Truth" AKA Correspondence
. This allows you to essentially describe some transformation of the collection, and then assert on the result of that transformation. In this case we'll create an INSTANCEOF_CORRESPONDENCE
:
private static final Correspondence<Object, Class<?>> INSTANCEOF_CORRESPONDENCE =
new Correspondence<Object, Class<?>>() {
@Override
public boolean compare(@Nullable Object actual, @Nullable Class<?> expected) {
return expected.isInstance(actual);
}
@Override
public String toString() {
return "is instanceof";
}
};
Now you can write a nice one-liner!
assertThat(ls).comparingElementsUsing(INSTANCEOF_CORRESPONDENCE)
.containsExactly(String.class);
The big benefit of this approach over custom subjects is it's much more extensible - if you decide to make a different assertion the Correspondence
implementation doesn't need to change, just your assertion:
assertThat(ls).comparingElementsUsing(INSTANCEOF_CORRESPONDENCE)
.doesNotContain(Integer.class);
There are also tentative plans to support method references and lambdas with .comparingElementsUsing()
so that you'll be able to write something like:
assertThat(ls).comparingElementsUsing((o, c) -> c.isInstance(o), "is instanceof")
.containsExactly(String.class);