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

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

我正在为ClassA编写unit testing,因此使用Mockito模拟ClassB,并将其注入ClassA。 然后我想在不同的场景中validation传递给ClassB的列表的内容(即我的模拟)

我已经将代码删除了以下内容

public class ClassA implements Runnable { private final ClassB classB; public ClassA(final ClassB classB) { this.classB = classB; } public List getFilesFromDirectories() { final List newFileList = new ArrayList(); // ... return newFileList; } public void run() { final List 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 expectedFileList = createSortedExpectedFileList(testFileOne, testFileTwo, testFileThree); objUndertest.run(); verify(mockClassB).sendEvent(expectedFileList); } private List createSortedExpectedFileList(final File... files) { final List expectedFileList = new ArrayList(); for (final File file : files) { expectedFileList.add(file); } Collections.sort(expectedFileList); return expectedFileList; } } 

问题是这个测试在Windows上完全正常,但在Linux上失败了。 原因是在Windows上,ClassA列出文件的顺序与expectedList匹配,所以该行

 verify(mockClassB).sendEvent(expectedFileList); 

在Windows上导致问题excetdFileList = {FileA,FileB,FileC},而在Linux上它将是{FileC,FileB,FileA},因此validation失败。

问题是,我如何在Mockito中解决这个问题。 有什么方法可以说,我希望用这个参数调用这个方法,但我不关心列表内容的顺序。

我确实有一个解决方案,我只是不喜欢它,我宁愿有一个更清洁,更容易阅读的解决方案。

我可以使用ArgumentCaptor获取传递给mock的实际值,然后对其进行排序,并将其与我的预期值进行比较。

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

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

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

如果这是你可能在多次测试中做的事情,你最好使用适当的Mockito Matchers或Hamcrest 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  Matcher> sameAsSet(final List expectedList) { return new BaseMatcher>(){ @Override public boolean matches(Object o) { List actualList = Collections.EMPTY_LIST; try { actualList = (List) o; } catch (ClassCastException e) { return false; } Set expectedSet = new HashSet(expectedList); Set actualSet = new HashSet(actualList); return actualSet.equals(expectedSet); } @Override public void describeTo(Description description) { description.appendText("should contain all and only elements of ").appendValue(expectedList); } }; } } 

然后validation码变为:

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

如果您改为创建了mockito匹配器,则不需要argThat ,它基本上将一个hamcrest匹配器包装在mockito匹配器中。

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

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

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