模拟;使用列表调用验证方法,忽略列表中元素的顺序 [英] Mockito; verify method was called with list, ignore order of elements in list

查看:36
本文介绍了模拟;使用列表调用验证方法,忽略列表中元素的顺序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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 的列表的内容(即我的模拟)

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)

我已将代码剥离为以下内容

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 上运行良好,但在 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 上会导致问题 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 中解决这个问题.有没有办法说,我希望这个方法被这个参数调用,但是我不关心列表内容的顺序.

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.

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

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

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

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

推荐答案

在另一个答案中指出,如果您不关心订单,您可能最好更改界面,使其不关心订单.

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.代码有点混乱.

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.

如果这是您可能在多个测试中执行的操作,则最好使用适当的 Mockito MatchersHamcrest 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.

对于本示例,您可以按如下方式创建 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)));

如果您改为创建模拟匹配器,则不需要 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.

这会将排序或转换的逻辑移出您的测试并使其可重用.

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

这篇关于模拟;使用列表调用验证方法,忽略列表中元素的顺序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆