java 莫基托; 使用列表调用验证方法,忽略列表中元素的顺序

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25700059/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 08:29:06  来源:igfitidea点击:

Mockito; verify method was called with list, ignore order of elements in list

javajunitmockingmockito

提问by Dace

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) 可以获取目录中的文件。它扫描给定目录以查找与正则表达式匹配的文件。对于每个匹配的文件,它将文件对象添加到列表中。一旦目录被处理,它将文件列表传递给另一个类(ClassB)进行处理

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)

我正在为 ClassA 编写单元测试,所以我使用 Mockito 模拟 ClassB,并将其注入 ClassA。然后我想在不同的场景中验证传递给 ClassB 的列表的内容(即我的模拟)

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);
        }
    }
}

The test class looks like this

测试类看起来像这样

    @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;
    }
}

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

问题是这个测试在 Windows 上运行得很好,但在 Linux 上失败了。原因是在 Windows 上,ClassA 列出文件的顺序与 expectedList 相匹配,因此该行

verify(mockClassB).sendEvent(expectedFileList);

is causing the problem expecetdFileList = {FileA, FileB, FileC} on Windows, whereas on Linux it will be {FileC, FileB, FileA}, so the verify fails.

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

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.

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

I do have a solution, I just don't like it, I would rather have a cleaner, easier to read solution.

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

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.

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

    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);

采纳答案by Don Roby

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.

如另一个答案所述,如果您不关心顺序,则最好更改界面,使其不关心顺序。

If order matters in the code but not in a specific test, you can use the ArgumentCaptoras you did. It clutters the code a bit.

如果顺序在代码中很重要但在特定测试中不重要,您可以ArgumentCaptor像以前一样使用。它使代码有点混乱。

If this is something you might do in multiple tests, you might do better to use appropriate Mockito Matchersor 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.

如果这是您可能在多个测试中做的事情,您最好使用适当的Mockito MatchersHamcrest Matchers,或者自己动手(如果您没有找到满足需要的)。hamcrest matcher 可能是最好的,因为它可以在除 mockito 之外的其他上下文中使用。

For this example you could create a hamcrest matcher as follows:

对于此示例,您可以创建一个 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);
            }
        };
    }
}

And then the verify code becomes:

然后验证码变为:

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

If you instead created a mockito matcher, you wouldn't need the argThat, which basically wraps a hamcrest matcher in a mockito matcher.

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

This moves the logic of sorting or converting to set out of your test and makes it reusable.

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

回答by Alex Bishop

An ArgumentCaptorprobably is the best way to do what you want.

一种ArgumentCaptor可能是你想要的东西的最佳方式。

However, it seems that you don't actually care about the order of the files in the List. Therefore, have you considered changing ClassBso that it takes an unordered collection (like a Set) instead?

但是,您似乎并不真正关心List. 因此,您是否考虑过更改ClassB以使用无序集合(例如 a Set)?

回答by Chei

You can use an ArgumentCaptor and then Hamcrest's Matchers.containsInAnyOrder() for assertion like this:

您可以使用 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");