Проверка наличия в списке определенных обязательных и определенных необязательных элементов

Я пытался создать модульный тест, который бы удостоверился, что список (или, в более общем смысле, контейнер) содержит определенные обязательные элементы, позволяя ему также содержать некоторые дополнительные необязательные элементы (но, опять же, из заранее определенного списка параметров).

Предположим для определенности, что список:

  1. должен содержать элементы foo и bar;
  2. может содержать элемент optional;
  3. не может содержать никаких других элементов.

В Java, используя удобную функцию satisfiesAnyOf() из библиотеки AssertJ, можно написать этот тест следующее:

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"));
    }
}

Проблема с этим кодом заключается в том, что он компилируется только с использованием компилятора Java 8, а компиляторы Java 9+ отклоняют его с помощью:

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)

Клиентский код можно легко исправить, явно приведя it в каждой лямбде от List<? extends String> до List<String>, что даже не меняет сгенерированный байт-код:

        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"));

Глядя на API AssertJ, я не могу понять, как именно можно решить указанную выше проблему без небезопасного приведения типов.

Говоря в пользу Kotlin, эквивалентный код Kotlin компилируется просто отлично (тип it может быть указан как MutableList<out String> или 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") })
  }
}

Вопросы:

  1. Что именно изменилось в JLS между Java 8 и Java 9? Не могли бы вы указать мне точный раздел спецификации, запрещающий успешную компиляцию?
  2. Как можно переписать приведенный выше код Java, чтобы избежать явного приведения типов?

person Bass    schedule 26.05.2020    source источник
comment
NB: лучше делать (List<String> it) -> assertThat(..., чем кастинг.   -  person Andy Turner    schedule 27.05.2020
comment
@AndyTurner Действительно, но это не компилируется. Тип лямбды, ожидаемого satisfiesAnyOf(), должен быть Consumer<List<? extends String>>.   -  person Bass    schedule 27.05.2020


Ответы (1)


Какую версию AssertJ вы используете?

Ошибка, решенная с Java 9 в AssertJ Core 3.15.0. Я бы попробовал с 3.16 и посмотрел, как пойдет, если вы еще не пробовали.

person Vargan    schedule 26.05.2020
comment
Это последняя версия 3.16.1. Вы можете скачать проект здесь: github.com/unix-junkie/assertj-variance-issue - person Bass; 27.05.2020