web-dev-qa-db-ja.com

モッキート;リストでメソッドが呼び出されたことを確認し、リスト内の要素の順序を無視します

ディレクトリ内のファイルを取得するクラス(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では問題はexpecetdFileList = {FileA、FileB、FileC}ですが、Linuxでは{FileC、FileB、FileA}になるため、検証は失敗します。

問題は、Mockitoでこれを回避する方法です。言い方はありますか?このメソッドはこのパラメーターで呼び出されると思いますが、リストの内容の順序は気にしません。

私は解決策を持っていますが、私はそれが好きではありません。むしろ、よりクリーンで読みやすい解決策が欲しいです。

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

別の回答で述べたように、順序を気にしない場合は、インターフェースを変更して、順序を気にしないようにすることをお勧めします。

コードで順序が重要で特定のテストでは重要でない場合は、以前と同じようにArgumentCaptorを使用できます。それはコードを少し散らかします。

これが複数のテストで行う可能性があるものである場合は、適切な Mockito Matchers または Hamcrest Matchers を使用するか、自分でロールする(見つからない場合)それはニーズを満たす)。 mockito以外のコンテキストで使用できるため、hamcrestマッチャーが最適です。

この例では、次のように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マッチャーを作成した場合、基本的にはhamcrestマッチャーをmockitoマッチャーでラップするargThatは必要ありません。

これにより、ソートまたは変換のロジックがテストから外れて、再利用可能になります。

19
Don Roby

ArgumentCaptorは、おそらくあなたが望むことをするための最良の方法です。

ただし、List内のファイルの順序は実際には気にしていないようです。したがって、代わりにClassBを変更して、順序付けされていないコレクション(Setなど)を取得することを検討しましたか?

6
Alex Bishop