Подтвердить что ты не робот

Mockito; метод проверки вызывается со списком, игнорирует порядок элементов в списке

У меня есть класс (ClassA), который получает файлы в каталоге. Он сканирует данный каталог для файлов, соответствующих регулярному выражению. Для каждого соответствующего файла он добавляет объект File в список. После обработки каталога он передает список файлов другому классу (ClassB) для обработки

Я пишу модульные тесты для ClassA, поэтому я издеваюсь над ClassB, используя Mockito, и вводя его в ClassA. Затем я хочу проверить в разных сценариях содержимое списка, который передается ClassB (т.е. Мой макет)

Я отключил код до следующего

public class ClassA implements Runnable {

    private final ClassB classB;

    public ClassA(final ClassB classB) {
        this.classB = classB;
    }

    public List<File> getFilesFromDirectories() {
        final List<File> newFileList = new ArrayList<File>();
        //        ...
        return newFileList;
    }

    public void run() {
        final List<File> fileList = getFilesFromDirectories();

        if (fileList.isEmpty()) {
            //Log Message
        } else {
            classB.sendEvent(fileList);
        }
    }
}

Класс тестирования выглядит следующим образом

    @RunWith(MockitoJUnitRunner.class)
    public class AppTest {

    @Rule
    public TemporaryFolder folder = new TemporaryFolder();

    @Mock
    private ClassB mockClassB;

    private File testFileOne;

    private File testFileTwo;

    private File testFileThree;

    @Before
    public void setup() throws IOException {
        testFileOne = folder.newFile("testFileA.txt");
        testFileTwo = folder.newFile("testFileB.txt");
        testFileThree = folder.newFile("testFileC.txt");
    }

    @Test
    public void run_secondFileCollectorRun_shouldNotProcessSameFilesAgainBecauseofDotLastFile() throws Exception {
        final ClassA objUndertest = new ClassA(mockClassB);

        final List<File> expectedFileList = createSortedExpectedFileList(testFileOne, testFileTwo, testFileThree);
        objUndertest.run();

        verify(mockClassB).sendEvent(expectedFileList);
    }

    private List<File> createSortedExpectedFileList(final File... files) {
        final List<File> expectedFileList = new ArrayList<File>();
        for (final File file : files) {
            expectedFileList.add(file);
        }
        Collections.sort(expectedFileList);
        return expectedFileList;
    }
}

Проблема в том, что этот тест отлично работает на Windows, но не работает в Linux. Причина в том, что в окнах порядок, в котором ClassA отображает файлы, соответствует ожидаемому списку, поэтому строка

verify(mockClassB).sendEvent(expectedFileList);

вызывает проблему expecetdFileList = {FileA, FileB, FileC} в Windows, тогда как в Linux это будет {FileC, FileB, FileA}, поэтому проверка не выполняется.

Вопрос в том, как мне обойти это в Mockito. Есть ли способ сказать, я ожидаю, что этот метод будет вызван с этим параметром, но меня не интересует порядок содержимого списка.

У меня есть решение, мне просто не нравится, я бы предпочел бы более чистое, удобное для чтения решение.

Я могу использовать ArgumentCaptor, чтобы получить фактическое значение, переданное в mock, затем можно отсортировать его и сравнить его с моими ожидаемыми значениями.

    final ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class);
    verify(mockClassB).method(argument.capture());
    Collections.sort(expected);
    final List<String> value = argument.getValue();
    Collections.sort(value);
    assertEquals(expecetdFileList, value);
4b9b3361

Ответ 1

Как указано в другом ответе, если вы не заботитесь о заказе, вы можете лучше всего изменить интерфейс, чтобы он не заботился о заказе.

Если порядок имеет значение в коде, но не в конкретном тесте, вы можете использовать ArgumentCaptor, как и вы. Он немного загромождает код.

Если вы можете сделать это в нескольких тестах, вам лучше использовать соответствующие Mockito Matchers или Matrix, или сверните свой собственный (если вы не найдете тот, который заполняет эту потребность). Может быть, лучше всего использовать совпадение hamcrest, поскольку он может использоваться в других контекстах помимо mockito.

В этом примере вы можете создать сопоставление hamcrest следующим образом:

import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class MyMatchers {
    public  static <T> Matcher<List<T>> sameAsSet(final List<T> expectedList) {
        return new BaseMatcher<List<T>>(){
            @Override
            public boolean matches(Object o) {
                List<T> actualList = Collections.EMPTY_LIST;
                try {
                    actualList = (List<T>) o;
                }
                catch (ClassCastException e) {
                    return false;
                }
                Set<T> expectedSet = new HashSet<T>(expectedList);
                Set<T> actualSet = new HashSet<T>(actualList);
                return actualSet.equals(expectedSet);
            }

            @Override
            public void describeTo(Description description) {
                description.appendText("should contain all and only elements of ").appendValue(expectedList);
            }
        };
    }
}

И тогда код проверки будет выглядеть следующим образом:

verify(mockClassB).sendEvent(argThat(MyMatchers.sameAsSet(expectedFileList)));

Если вместо этого вы создали совпадение mockito, вам не понадобится argThat, который в основном обертывает совпадение hamcrest в совпадении mockito.

Это перемещает логику сортировки или преобразования, чтобы установить из вашего теста и делает его повторно используемым.

Ответ 2

An ArgumentCaptor, вероятно, лучший способ сделать то, что вы хотите.

Однако, похоже, что вы действительно не заботитесь о порядке файлов в List. Поэтому рассмотрели ли вы изменение ClassB, чтобы вместо него взять неупорядоченный набор (например, Set)?