問題描述
我有一個類 (ClassA) 可以獲取目錄中的文件.它掃描給定目錄以查找匹配正則表達(dá)式的文件.對于每個匹配的文件,它會將一個文件對象添加到列表中.處理完目錄后,會將文件列表傳遞給另一個類(ClassB)進(jìn)行處理
I have a class (ClassA) that get the files in a directory. It scans the given directory for files matching a regex. For each matching file, it adds a File Object to a list. Once the directory is processed, it passes the List of Files to another Class (ClassB) for processing
我正在為 ClassA 編寫單元測試,所以我正在使用 Mockito 模擬 ClassB,并將其注入 ClassA.然后我想在不同的場景中驗證傳遞給 ClassB 的列表的內(nèi)容(即我的模擬)
I am writing unit tests for ClassA, so am mocking ClassB using Mockito, and injecting it into ClassA. I then want to verify in different scenarios the contents of the list that is passed to ClassB (ie my mock)
我已將代碼剝離為以下內(nèi)容
I've stripped back the code to the following
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 上運(yùn)行良好,但在 Linux 上失敗.原因是在windows上,ClassA列出文件的順序與expectedList相匹配,所以行
The problem is that this test works perfectly fine on windows, but fails on Linux. The reason being that on windows, the order that ClassA list the files matches the expectedList, so the line
verify(mockClassB).sendEvent(expectedFileList);
在 Windows 上會導(dǎo)致問題 expecetdFileList = {FileA, FileB, FileC},而在 Linux 上會是 {FileC, FileB, FileA},因此驗證失敗.
is causing the problem expecetdFileList = {FileA, FileB, FileC} on Windows, whereas on Linux it will be {FileC, FileB, FileA}, so the verify fails.
問題是,我如何在 Mockito 中解決這個問題.有沒有辦法說,我希望這個方法被這個參數(shù)調(diào)用,但是我不關(guān)心列表內(nèi)容的順序.
The question is, how do I get around this in Mockito. Is there any way of saying, I expect this method to be be called with this parameter, but I don't care about the order of the contents of the list.
我確實(shí)有一個解決方案,我只是不喜歡它,我寧愿有一個更干凈、更易于閱讀的解決方案.
I do have a solution, I just don't like it, I would rather have a cleaner, easier to read solution.
我可以使用 ArgumentCaptor 獲取傳遞給模擬的實(shí)際值,然后對其進(jìn)行排序,并將其與我的預(yù)期值進(jìn)行比較.
I can use an ArgumentCaptor to get the actual value passed into the mock, then can sort it, and compare it to my expected values.
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);
推薦答案
在另一個答案中指出,如果您不關(guān)心訂單,您可能最好更改界面,使其不關(guān)心訂單.
As noted in another answer, if you don't care about the order, you might do best to change the interface so it doesn't care about the order.
如果順序在代碼中很重要,但在特定測試中不重要,您可以像以前一樣使用 ArgumentCaptor
.代碼有點(diǎn)混亂.
If order matters in the code but not in a specific test, you can use the ArgumentCaptor
as you did. It clutters the code a bit.
如果這是您可能在多個測試中執(zhí)行的操作,則最好使用適當(dāng)?shù)?Mockito Matchers 或 Hamcrest Matchers,或者自己動手制作(如果你找不到滿足需要的).hamcrest matcher 可能是最好的,因為它可以在 mockito 之外的其他上下文中使用.
If this is something you might do in multiple tests, you might do better to use appropriate Mockito Matchers or Hamcrest Matchers, or roll your own (if you don't find one that fills the need). A hamcrest matcher might be best as it can be used in other contexts besides mockito.
對于本示例,您可以按如下方式創(chuàng)建 hamcrest 匹配器:
For this example you could create a hamcrest matcher as follows:
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)));
如果您改為創(chuàng)建模擬匹配器,則不需要 argThat
,它基本上將 hamcrest 匹配器包裝在模擬匹配器中.
If you instead created a mockito matcher, you wouldn't need the argThat
, which basically wraps a hamcrest matcher in a mockito matcher.
這會將排序或轉(zhuǎn)換的邏輯移出您的測試并使其可重用.
This moves the logic of sorting or converting to set out of your test and makes it reusable.
這篇關(guān)于模擬;使用列表調(diào)用驗證方法,忽略列表中元素的順序的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網(wǎng)!