在没有模拟迭代器的情况下模拟 DirectoryStream<Path> 可能吗?

Mocking DirectoryStream<Path> without mocking iterator possible?

我有以下代码:

        Map<String, String> fileContentsByName = new HashMap<String, String>();

        try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory))
        {
            for (Path path : directoryStream)
            {
                if (Files.isRegularFile(path))
                {
                    fileContentsByName.put(path.getFileName().toString(), new String(Files.readAllBytes(path)));
                }
            }
        }
        catch (IOException e)
        {
        }

我正在尝试测试这个方法。我正在使用 Powermock 来获取模拟的 DirectoryStream<Path>。然而,当测试在代码中遇到 for-each 时,它就炸毁了一个 NPE。如何在 DirectoryStream 中指定路径?

我考虑过更改源代码以使用迭代器并模拟 DirectoryStream 的迭代器以提供所需的路径,但我想知道是否有更好的选择?

假设您在上面提供的代码片段在 class 中定义如下:

public class DirectoryStreamReader {

    public Map<String, String> read(Path directory) {

        Map<String, String> fileContentsByName = new HashMap<String, String>();
        try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory)) {
            for (Path path : directoryStream) {
                if (Files.isRegularFile(path)) {
                    fileContentsByName.put(path.getFileName().toString(), new String(Files.readAllBytes(path)));
                }
            }
        } catch (IOException e) {
        }

        return fileContentsByName;
    }
}

那么下面的测试就会通过:

@RunWith(PowerMockRunner.class)
@PrepareForTest({DirectoryStreamReader.class})
public class DirectoryStreamTest {

    @Rule
    public TemporaryFolder folder= new TemporaryFolder();

    @Test
    public void canReadFilesUsingDirectoryStream() throws IOException {
        PowerMockito.mockStatic(Files.class);

        Path directory = Mockito.mock(Path.class);
        DirectoryStream<Path> expected = Mockito.mock(DirectoryStream.class);
        Mockito.when(Files.newDirectoryStream(Mockito.any(Path.class))).thenReturn(expected);

        File fileOne = folder.newFile();
        File fileTwo = folder.newFile();
        Iterator<Path> directoryIterator = Lists.newArrayList(Paths.get(fileOne.toURI()),
                Paths.get(fileTwo.toURI())).iterator();

        Mockito.when(expected.iterator()).thenReturn(directoryIterator);

        Mockito.when(Files.isRegularFile(Mockito.any(Path.class))).thenReturn(true);
        Mockito.when(Files.readAllBytes(Mockito.any(Path.class))).thenReturn("fileOneContents".getBytes()).thenReturn("fileTwoContents".getBytes());

        Map<String, String> fileContentsByName = new DirectoryStreamReader().read(directory);

        Assert.assertEquals(2, fileContentsByName.size());
        Assert.assertTrue(fileContentsByName.containsKey(fileOne.getName()));
        Assert.assertEquals("fileOneContents", fileContentsByName.get(fileOne.getName()));
        Assert.assertTrue(fileContentsByName.containsKey(fileTwo.getName()));
        Assert.assertEquals("fileTwoContents", fileContentsByName.get(fileTwo.getName()));
    }
}

这里的重点是:

  • 使用 JUnit 的 TemporaryFolder 规则创建和丢弃一些供测试使用的文件
  • 使用 PowerMockito 模拟与 java.nio.file.Files 的所有交互,这是最终的 class 并且被模拟的方法是静态的,因此需要 PowerMockito
  • mocking a system class 时遵循 PowerMockito 建议,特别是:
    • 在测试用例的 class 级别使用 @RunWith(PowerMockRunner.class) 注释。
    • 在测试用例的 class 级别使用 @PrepareForTest({ClassThatCallsTheSystemClass.class}) 注释。
    • 使用mockStatic(SystemClass.class)模拟系统class
  • 此测试已通过 Junit 4.12、Mockito 2.7.19 和 PowerMock 1.7.0 验证