我有一个类(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上导致问题excetdFileList = {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);
解决方法:
如另一个答案所述,如果您不关心订单,您可能最好更改界面,以便它不关心订单.
如果订单在代码中有问题但在特定测试中不重要,则可以像使用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 <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匹配器中.
这会将排序或转换的逻辑移动到您的测试之外并使其可重用.