的Mockito;使用list调用verify方法,忽略列表中元素的顺序

时间:2014-09-06 12:07:51

标签: java junit mocking mockito

我有一个类(ClassA)来获取目录中的文件。它扫描给定目录中与正则表达式匹配的文件。对于每个匹配的文件,它将文件对象添加到列表中。 处理目录后,它会将文件列表传递给另一个类(ClassB)进行处理

我正在为ClassA编写单元测试,因此我使用Mockito模拟ClassB,并将其注入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上失败了。原因是在Windows上,ClassA列出文件的顺序与expectedList匹配,所以行

verify(mockClassB).sendEvent(expectedFileList);

在Windows上导致问题expecetdFileList = {FileA,FileB,FileC},而在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);

3 个答案:

答案 0 :(得分:18)

如另一个答案所述,如果您不关心订单,您可能最好更改界面,以便它不关心订单。

如果订单在代码中有问题但在特定测试中不重要,则可以像使用ArgumentCaptor一样使用。它使代码混乱了一些。

如果您可以在多个测试中执行此操作,则最好使用适当的Mockito MatchersHamcrest Matchers,或者自己动手(如果您找不到填写需要)。 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匹配器中。

这会将排序或转换的逻辑移到测试中并使其可重复使用。

答案 1 :(得分:6)

ArgumentCaptor可能是做你想做的最好的方法。

但是,您似乎并不真正关心List中文件的顺序。因此,您是否考虑过更改ClassB以便它采用无序集合(例如Set)?

答案 2 :(得分:0)

您可以使用ArgumentCaptor,然后使用Hamcrest的Matchers.containsInAnyOrder()进行这样的声明:

ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class);
verify(mockClassB).method(argument.capture());
List<String> value = argument.getValue();
assertThat(value, containsInAnyOrder("expected", "values");