In JUnit 4 it was easy to test invariants across a bunch of classes by using the #Parameterized annotation. The key thing is that a collection of tests are being run against a single list of arguments.
How to replicate this in JUnit 5, without using JUnit-vintage?
#ParameterizedTest is not applicable to a test class. #TestTemplate sounded like it might be appropriate, but that annotation's target is also a method.
An example of such a JUnit 4 test is:
#RunWith( Parameterized.class )
public class FooInvariantsTest{
#Parameterized.Parameters
public static Collection<Object[]> data(){
return new Arrays.asList(
new Object[]{ new CsvFoo() ),
new Object[]{ new SqlFoo() ),
new Object[]{ new XmlFoo() ),
);
}
private Foo fooUnderTest;
public FooInvariantsTest( Foo fooToTest ){
fooUnderTest = fooToTest;
}
#Test
public void testInvariant1(){
...
}
#Test
public void testInvariant2(){
...
}
}
The parameterized test feature in JUnit 5 doesn't provide the exact same features than those provided by JUnit 4.
New features with more flexibility were introduced... but it also lost the JUnit4 feature where the parameterized test class uses the parameterized fixtures/assertions at the class level that is for all test methods of the class.
Defining #ParameterizedTest for each test method by specifying the "input" is so needed.
Beyond that lack I will present the main differences between the 2 versions and how to use parameterized tests in JUnit 5.
TL;DR
To write a parameterized test that specifies a value by case to test as your in your question,
org.junit.jupiter.params.provider.MethodSource should do the job.
#MethodSource allows you to refer to one or more methods of the test
class. Each method must return a Stream, Iterable, Iterator, or array
of arguments. In addition, each method must not accept any arguments.
By default such methods must be static unless the test class is
annotated with #TestInstance(Lifecycle.PER_CLASS).
If you only need a single parameter, you can return instances of the
parameter type directly as demonstrated by the following example.
As JUnit 4, #MethodSource relies on a factory method and may also be used for test methods that specify multiple arguments.
In JUnit 5, it is the way of writing parameterized tests the closest to JUnit 4.
JUnit 4 :
#Parameters
public static Collection<Object[]> data() {
JUnit 5 :
private static Stream<Arguments> data() {
Main improvements :
Collection<Object[]> is become Stream<Arguments> that provides more flexibility.
the way of binding the factory method to the test method differs a little.
It is now shorter and less error prone : no more requirement to create a constructor and declares field to set the value of each parameter. The binding of the source is done directly on the parameters of the test method.
With JUnit 4, inside a same class, one and only one factory method has to be declared with #Parameters.
With JUnit 5, this limitation is lifted : multiple methods may indeed be used as factory method.
So, inside the class, we can so declare some test methods annotated with #MethodSource("..") that refer different factory methods.
For example here is a sample test class that asserts some addition computations :
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.api.Assertions;
public class ParameterizedMethodSourceWithArgumentsTest {
#ParameterizedTest
#MethodSource("addFixture")
void add(int a, int b, int result) {
Assertions.assertEquals(result, a + b);
}
private static Stream<Arguments> addFixture() {
return Stream.of(
Arguments.of(1, 2, 3),
Arguments.of(4, -4, 0),
Arguments.of(-3, -3, -6));
}
}
To upgrade existing parameterized tests from JUnit 4 to JUnit 5, #MethodSource is a candidate to consider.
Summarize
#MethodSource has some strengths but also some weaknesses.
New ways to specify sources of the parameterized tests were introduced in JUnit 5.
Here some additional information (far being exhaustive) about them that I hope could give a broad idea on how deal with in a general way.
Introduction
JUnit 5 introduces parameterized tests feature in these terms :
Parameterized tests make it possible to run a test multiple times with
different arguments. They are declared just like regular #Test methods
but use the #ParameterizedTest annotation instead. In addition, you
must declare at least one source that will provide the arguments for
each invocation.
Dependency requirement
Parameterized tests feature is not included in the junit-jupiter-engine core dependency.
You should add a specific dependency to use it : junit-jupiter-params.
If you use Maven, this is the dependency to declare :
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.0.0</version>
<scope>test</scope>
</dependency>
Sources available to create data
Contrary to JUnit 4, JUnit 5 provides multiple flavors and artifacts to write parameterized tests
The ways to favor depend generally on the source of data you want to use.
Here are the source types proposed by the framework and described in the documentation :
#ValueSource
#EnumSource
#MethodSource
#CsvSource
#CsvFileSource
#ArgumentsSource
Here are the 3 main sources I actually use with JUnit 5 and I will present:
#MethodSource
#ValueSource
#CsvSource
I consider them as basic as I write parameterized tests. They should allow to write in JUnit 5, the type of JUnit 4 tests that you described.
#EnumSource, #ArgumentsSource and #CsvFileSource may of course be helpful but they are more specialized.
Presentation of #MethodSource, #ValueSource and #CsvSource
1) #MethodSource
This type of source requires to define a factory method.
But it also provides much flexibility.
In JUnit 5, it is the way of writing parameterized tests the closest to JUnit 4.
If you have a single method parameter in the test method and you want to use any type as source, #MethodSource is a very good candidate.
To achieve it, define a method that returns a Stream of the value for each case and annotate the test method with #MethodSource("methodName") where methodName is the name of this data source method.
For example, you could write :
import java.util.stream.Stream;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
public class ParameterizedMethodSourceTest {
#ParameterizedTest
#MethodSource("getValue_is_never_null_fixture")
void getValue_is_never_null(Foo foo) {
Assertions.assertNotNull(foo.getValue());
}
private static Stream<Foo> getValue_is_never_null_fixture() {
return Stream.of(new CsvFoo(), new SqlFoo(), new XmlFoo());
}
}
If you have multiple method parameters in the test method and you want to use any type as source, #MethodSource is also a very good candidate.
To achieve it, define a method that returns a Stream of org.junit.jupiter.params.provider.Arguments for each case to test.
For example, you could write :
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.api.Assertions;
public class ParameterizedMethodSourceWithArgumentsTest {
#ParameterizedTest
#MethodSource("getFormatFixture")
void getFormat(Foo foo, String extension) {
Assertions.assertEquals(extension, foo.getExtension());
}
private static Stream<Arguments> getFormatFixture() {
return Stream.of(
Arguments.of(new SqlFoo(), ".sql"),
Arguments.of(new CsvFoo(), ".csv"),
Arguments.of(new XmlFoo(), ".xml"));
}
}
2)#ValueSource
If you have a single method parameter in the test method and you may represent the source of the parameter from one of these built-in types (String, int, long, double), #ValueSource suits.
#ValueSource defines indeed these attributes :
String[] strings() default {};
int[] ints() default {};
long[] longs() default {};
double[] doubles() default {};
You could for example use it in this way :
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
public class ParameterizedValueSourceTest {
#ParameterizedTest
#ValueSource(ints = { 1, 2, 3 })
void sillyTestWithValueSource(int argument) {
Assertions.assertNotNull(argument);
}
}
Beware 1) you must not specify more than one annotation attribute.
Beware 2) The mapping between the source and the parameter of the method can be done between two distinct types.
The type String used as source of data allows particularly, thanks to its parsing, to be converted into multiple other types.
3) #CsvSource
If you have multiple method parameters in the test method, a #CsvSource may suit.
To use it, annotate the test with #CsvSource and specify in a array of String each case.
Values of each case are separated by a comma.
Like #ValueSource, the mapping between the source and the parameter of the method can be done between two distinct types.
Here is an example that illustrates that :
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
public class ParameterizedCsvSourceTest {
#ParameterizedTest
#CsvSource({ "12,3,4", "12,2,6" })
public void divideTest(int n, int d, int q) {
Assertions.assertEquals(q, n / d);
}
}
#CsvSource VS #MethodSource
These source types serve a very classic requirement : mapping from the source to multiple method parameters in the test method.
But their approach is different.
#CsvSource has some advantages : it is clearer and shorter.
Indeed, parameters are defined just above the tested method, no requirement to create a fixture method that may in addition generate "unused" warnings.
But it also has an important limitation concerning mapping types.
You have to provide an array of String. The framework provides conversion features but it is limited.
To summarize, while the String provided as source and the parameters of the test method have the same type (String->String) or rely on built-in conversion (String->int for example), #CsvSource appears as the way to use.
As it is not the case, you have to make a choice between
keeping the flexibility of #CsvSource by creating a custom converter (ArgumentConverter subclass) for conversions not performed by the framework or using #MethodSource with a factory method that
returns Stream<Arguments>. It has the drawbacks described above but it also has the great benefit to map out-of-the box any type from the source to the parameters.
Argument Conversion
About the mapping between the source (#CsvSource or #ValueSource for example) and the parameters of the test method, as seen, the framework allows to do some conversions if the types are not the same.
Here is a presentation of the two types of conversions :
3.13.3. Argument Conversion
Implicit Conversion
To support use cases like #CsvSource, JUnit Jupiter provides a number
of built-in implicit type converters. The conversion process depends
on the declared type of each method parameter.
.....
String instances are currently implicitly converted to the following
target types.
Target Type | Example
boolean/Boolean | "true" → true
byte/Byte | "1" → (byte) 1
char/Character | "o" → 'o'
short/Short | "1" → (short) 1
int/Integer | "1" → 1
.....
For example in the previous example, an implicit conversion is done between String from source and int defined as parameter:
#CsvSource({ "12,3,4", "12,2,6" })
public void divideTest(int n, int d, int q) {
Assertions.assertEquals(q, n / d);
}
And here, an implicit conversion is done from String source to LocalDate parameter:
#ParameterizedTest
#ValueSource(strings = { "2018-01-01", "2018-02-01", "2018-03-01" })
void testWithValueSource(LocalDate date) {
Assertions.assertTrue(date.getYear() == 2018);
}
If for two types, no conversion is provided by the framework,
which is the case for custom types, you should use an ArgumentConverter.
Explicit Conversion
Instead of using implicit argument conversion you may explicitly
specify an ArgumentConverter to use for a certain parameter using the
#ConvertWith annotation like in the following example.
JUnit provides a reference implementation for clients who need to create a specific ArgumentConverter.
Explicit argument converters are meant to be implemented by test
authors. Thus, junit-jupiter-params only provides a single explicit
argument converter that may also serve as a reference implementation:
JavaTimeArgumentConverter. It is used via the composed annotation
JavaTimeConversionPattern.
Test method using this converter :
#ParameterizedTest
#ValueSource(strings = { "01.01.2017", "31.12.2017" })
void testWithExplicitJavaTimeConverter(#JavaTimeConversionPattern("dd.MM.yyyy") LocalDate argument) {
assertEquals(2017, argument.getYear());
}
JavaTimeArgumentConverter converter class :
package org.junit.jupiter.params.converter;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.Year;
import java.time.YearMonth;
import java.time.ZonedDateTime;
import java.time.chrono.ChronoLocalDate;
import java.time.chrono.ChronoLocalDateTime;
import java.time.chrono.ChronoZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalQuery;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.jupiter.params.support.AnnotationConsumer;
/**
* #since 5.0
*/
class JavaTimeArgumentConverter extends SimpleArgumentConverter
implements AnnotationConsumer<JavaTimeConversionPattern> {
private static final Map<Class<?>, TemporalQuery<?>> TEMPORAL_QUERIES;
static {
Map<Class<?>, TemporalQuery<?>> queries = new LinkedHashMap<>();
queries.put(ChronoLocalDate.class, ChronoLocalDate::from);
queries.put(ChronoLocalDateTime.class, ChronoLocalDateTime::from);
queries.put(ChronoZonedDateTime.class, ChronoZonedDateTime::from);
queries.put(LocalDate.class, LocalDate::from);
queries.put(LocalDateTime.class, LocalDateTime::from);
queries.put(LocalTime.class, LocalTime::from);
queries.put(OffsetDateTime.class, OffsetDateTime::from);
queries.put(OffsetTime.class, OffsetTime::from);
queries.put(Year.class, Year::from);
queries.put(YearMonth.class, YearMonth::from);
queries.put(ZonedDateTime.class, ZonedDateTime::from);
TEMPORAL_QUERIES = Collections.unmodifiableMap(queries);
}
private String pattern;
#Override
public void accept(JavaTimeConversionPattern annotation) {
pattern = annotation.value();
}
#Override
public Object convert(Object input, Class<?> targetClass) throws ArgumentConversionException {
if (!TEMPORAL_QUERIES.containsKey(targetClass)) {
throw new ArgumentConversionException("Cannot convert to " + targetClass.getName() + ": " + input);
}
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
TemporalQuery<?> temporalQuery = TEMPORAL_QUERIES.get(targetClass);
return formatter.parse(input.toString(), temporalQuery);
}
}
Related
I am just trying to go through with Spock test framework and as per the documentation it looks better than JUnit and tried to write the simple test case but somehow it's not working.
Class:
public class SwapTwoNumber {
public void swapNumber(int a,int b){
a =a+b;
b=a-b;
a=a-b;
}
}
Spock Test Groovy:
import spock.lang.Specification
class SwapTwoNumberTest extends Specification{
def "swap to number as given. #a and #b"() {
given:
int a = 5
int b = 6
when:
SwapTwoNumber sw =Mock()
sw.swapNumber(a,b)
then:
a==6
b==5
}
}
I am not familiar with the testing framework you mentioned but this is most likely not a framework problem. You are passing primitives arguments.
As stated in the docs
Primitive arguments, such as an int or a double, are passed into methods by value. This means that any changes to the values of the parameters exist only within the scope of the method. When the method returns, the parameters are gone and any changes to them are lost.
For example, if you have the following two ArrayLists:
ArrayList<String> arrLst1 = new ArrayList<String>();
arrLst1.add("Hello");
arrLst1.add("Goodbye");
ArrayList<String> arrLst2 = new ArrayList<String>();
arrLst2.add("Greetings");
arrLst2.add("See you soon");
If I wanted to use JUnit, my guess would be to use something like this:
import static org.junit.Assert.*;
import org.junit.Test;
import java.util.*;
public class myTest {
#Test
public void firstTest() {
assertArrayEquals("Error Message", arrLst1, arrLst2);
}
}
However, I'm having an issue where once I run the code, it states that the two values are equal. Looking at the documentation, I don't see anything for assertArrayEquals() for two String ArrayLists. Is this even something that is possible?
assertArrayEquals is for arrays, and ArrayList<T> is not an array.
JUnit5
Use JUnit5 and:
assertIterableEquals(expected, actual); //from org.junit.jupiter.api.Assertions
will assert that expected and actual iterables are deeply equal.
JUnit4
If you use JUnit4, upgrade to JUnit5; however, if still with 4, then, generally, you will need to override .equals() method in your Generic Type Argument class (T in List<T>), after which, you can use:
assertEquals(expected, actual);
Note, that if your generic argument (typed contained in your list) is String, you do not need (you cannot, actually) override .equals(), since String already overrides it perfectly fine.
You can use assertEquals, which has an overload taking two Object arguments, like so:
import static org.junit.Assert.*;
var a = List.of("hello");
var b = List.of("hello");
assertEquals(a, b); // passes
var c = List.of("hello");
var d = List.of("world");
assertEquals(c, d); // fails
In this case, assertEquals method uses the .equals() overridden method of List to determine if both lists contain the same contents.
I have a test case where I have provided my test data in the form of enums. Like
enum TestTransactions {
TestTransactions(Transaction T1, Transaction T2, String expectedOutput){}
}
In my Test class, I have to use it as:
class Test {
private final static int REPETITION_COUNT = TestTransactions.values().length;
#RepeatedTest(value=REPETITION_COUNT)
private void testAllTransactions(RepetitionInfo info) {
TestTransactions currentTest = TestTransactions.values()[info.getCurrentRepetition()];
logger.info("Executing test for " + currentTest.name());
setExpectationsFor(currentTest);
whenControllerIsCalled();
Assert.assertEquals(currentTest.getExpectedOutput(), result.getBody());
}
}
Here this line #RepeatedTest(value=REPETITION_COUNT) is giving compilation error saying "Attribute value must be constant."
Is there any way to achieve this? Though I have tried assigning REPETITION_COUNT (declared as final) inside constructor and static block as well and during declaration as shown in this example.
If I understand your use case correctly, you want to use #ParameterizedTest with #EnumSource instead of #RepatedTest - this is how JUnit5 supports such use case out of the box.
First, add dependency on org.junit.jupiter:junit-jupiter-params (which provides support for #ParameterizedTest) and then:
class Test {
#ParameterizedTest
#EnumSource
void testAllTransactions(TestTransactions currentTest) {
logger.info("Executing test for " + currentTest.name());
setExpectationsFor(currentTest);
whenControllerIsCalled();
Assertions.assertEquals(currentTest.getExpectedOutput(), result.getBody());
}
}
Also side notes for JUnit 5:
#Test methods should be package-private (no visibility qualifier), not private
use Assertions and not Assert
What you’re experiencing is a constraint of the Java compiler. Unless the Java language specification will be changed you cannot do what you want.
What you could do is make a feature request for Jupiter to also accept a value provider, e.g. of type Class<? extends Supplier<Integer>>. Or you could simulate it using Jupiter‘s dynamic tests feature.
I want to write 2 test cases for my class:
test with normal method invocation
test with method invocation which throws RuntimeException
For this I have created the following test. But unfortunately it doesn't work: it ends with "Too few invocations", zero instead of one.
Why?
Code:
import spock.lang.Shared
import spock.lang.Specification
class SimpleSpockTest extends Specification {
interface Simple {
void run();
}
#Shared
Simple good = Mock(Simple)
#Shared
Simple bad = Mock(Simple)
def "test invocations"() {
setup:
bad.run() >> { throw new RuntimeException() }
when:
instance.run()
then:
invocations * instance.run()
where:
instance | invocations
good | 1
bad | 1
}
}
PS
Also I would like to move "shared" instances to fixture "setup" but I cannot: it fails with saying that class SimpleSpockTest doesn't have property "good" or "bad".
Mock objects won't behave the way you want as #Shared fields. From Spock's documentation on Interaction Based Testing:
Interactions are always scoped to a particular feature method. Hence they cannot be declared in a static method, setupSpec method, or cleanupSpec method. Likewise, mock objects should not be stored in static or #Shared fields.
In other words, you have to define mocks inside of the feature method itself.
You mentioned that the provided example above is simplified, but if possible, you'll have to do something like the following:
def "test invocations"() {
setup:
Simple good = Mock(Simple)
Simple bad = Mock(Simple)
bad.run() >> { throw new RuntimeException() }
when:
good.run()
bad.run()
then:
1 * good.run()
1 * bad.run()
}
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!
A simpler workaround is
for (Object elt : myList) {
assertThat(elt).isInstanceOf(ExpectedType.class);
}
heenenee's answer is more elegant:
assertThat(iterable()).that(myList).containsInstancesOf(ExpectedType.class);
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);