I was trying to create a unit test which would make sure a list (or, more generally, a container) contains certain mandatory items, while allowing it to also contain some extra optional items (but, again, from a pre-defined list of options).
Let's assume, for definiteness, that the list:
- must contain items
foo
andbar
; - may contain item
optional
; - may not contain any other items.
In Java, using a handy satisfiesAnyOf()
function from the AssertJ library, this test can be written as follows:
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class C {
@Test
public void t0() {
doTest(asList("foo", "bar"));
}
@Test
public void t1() {
doTest(asList("foo", "bar", "optional"));
}
private static void doTest(final List<String> items) {
assertThat(items).as("items").satisfiesAnyOf(
it -> assertThat(it).as("XXX").containsExactlyInAnyOrder("foo", "bar"),
it -> assertThat(it).as("YYY").containsExactlyInAnyOrder("foo", "bar", "optional"));
}
}
The problem with this code is that it only compiles using a Java 8 compiler, with Java 9+ compilers rejecting it with:
C.java:[26,63] no suitable method found for containsExactlyInAnyOrder(java.lang.String,java.lang.String)
method org.assertj.core.api.AbstractIterableAssert.containsExactlyInAnyOrder(capture#1 of ? extends java.lang.String...) is not applicable
(varargs mismatch; java.lang.String cannot be converted to capture#1 of ? extends java.lang.String)
method org.assertj.core.api.ListAssert.containsExactlyInAnyOrder(capture#1 of ? extends java.lang.String...) is not applicable
(varargs mismatch; java.lang.String cannot be converted to capture#1 of ? extends java.lang.String)
C.java:[27,63] no suitable method found for containsExactlyInAnyOrder(java.lang.String,java.lang.String,java.lang.String)
method org.assertj.core.api.AbstractIterableAssert.containsExactlyInAnyOrder(capture#2 of ? extends java.lang.String...) is not applicable
(varargs mismatch; java.lang.String cannot be converted to capture#2 of ? extends java.lang.String)
method org.assertj.core.api.ListAssert.containsExactlyInAnyOrder(capture#2 of ? extends java.lang.String...) is not applicable
(varargs mismatch; java.lang.String cannot be converted to capture#2 of ? extends java.lang.String)
The client code can easily be fixed by explicitly casting it
in each lambda from List<? extends String>
to List<String>
which doesn't even change the generated bytecode:
assertThat(items).as("items").satisfiesAnyOf(
it -> assertThat((List<String>) it).as("XXX").containsExactlyInAnyOrder("foo", "bar"),
it -> assertThat((List<String>) it).as("YYY").containsExactlyInAnyOrder("foo", "bar", "optional"));
Looking at AssertJ API, I can't figure out how exactly the above issue can be addressed w/o unsafe casts.
Speaking in favour of Kotlin, the equivalent Kotlin code compiles just fine (the type of it
can be specified as either MutableList<out String>
or List<String>
):
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@RunWith(JUnit4::class)
class K {
@Test
fun t() {
val items = listOf("foo", "bar", "optional")
assertThat(items).`as`("items").satisfiesAnyOf({ assertThat(it).`as`("XXX").containsExactlyInAnyOrder("foo", "bar") },
{ assertThat(it).`as`("YYY").containsExactlyInAnyOrder("foo", "bar", "optional") })
}
}
Questions:
- What exactly has changed in the JLS between Java 8 and Java 9? Can you point me to the exact section of the specification which forbids successful compilation?
- How can the above Java code be rewritten to avoid explicit type casts?
>`.
– Bass May 26 '20 at 22:32