File System Mocking with Jimfs – 使用Jimfs的文件系统嘲弄

最后修改: 2019年 10月 26日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Typically, when testing components that make heavy use of I/O operations, our tests can suffer from several issues such as poor performance, platform dependency, and unexpected state.

通常情况下,在测试大量使用I/O操作的组件时,我们的测试会出现一些问题,如性能差、平台依赖性和意外状态。

In this tutorial, we’ll take a look at how we can alleviate these problems using the in-memory file system Jimfs.

在本教程中,我们将看看如何使用内存文件系统Jimfs缓解这些问题。

2. Introduction to Jimfs

2.Jimfs简介

Jimfs is an in-memory file system that implements the Java NIO API and supports almost every feature of it. This is particularly useful, as it means we can emulate a virtual in-memory filesystem and interact with it using our existing java.nio layer.

Jimfs是一个内存文件系统,它实现了Java NIO API,并支持其中的几乎所有功能。这一点特别有用,因为它意味着我们可以模拟一个虚拟的内存文件系统,并使用我们现有的java.nio层与之交互。

As we’re going to see, it may be beneficial to use a mocked file system instead of a real one in order to:

正如我们将要看到的,使用一个模拟的文件系统而不是一个真实的文件系统可能是有益的,以便。

  • Avoid being dependent on the file system that is currently running the test
  • Ensure the filesystem gets assembled with the expected state on each test run
  • Help speed up our tests

As file systems vary considerably, using Jimfs also facilitates easily testing with file systems from different operating systems.

由于文件系统差异很大,使用Jimfs也有利于轻松测试不同操作系统的文件系统。

3. Maven Dependencies

3.Maven的依赖性

First of all, let’s add the project dependencies we’ll need for our examples:

首先,让我们添加我们的例子需要的项目依赖。

<dependency>
    <groupId>com.google.jimfs</groupId>
    <artifactId>jimfs</artifactId>
    <version>1.1</version>
</dependency>

The jimfs dependency contains everything that we need in order to use our mocked file system. Additionally, we’ll be writing tests using JUnit5.

jimfs依赖性包含了我们使用模拟文件系统所需的一切。此外,我们将使用JUnit5编写测试。

4. A Simple File Repository

4.一个简单的文件库

We’ll start by defining a simple FileRepository class that implements some standard CRUD operations:

我们先定义一个简单的文件存储库类,实现一些标准的CRUD操作。

public class FileRepository {

    void create(Path path, String fileName) {
        Path filePath = path.resolve(fileName);
        try {
            Files.createFile(filePath);
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }

    String read(Path path) {
        try {
            return new String(Files.readAllBytes(path));
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }

    String update(Path path, String newContent) {
        try {
            Files.write(path, newContent.getBytes());
            return newContent;
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }

    void delete(Path path) {
        try {
            Files.deleteIfExists(path);
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }
}

As we can see, each method is making use of standard java.nio classes.

我们可以看到,每个方法都在利用标准的java.nio类。

4.1. Creating a File

4.1.创建一个文件

In this section, we’ll write a test that tests the create method from our repository:

在这一节中,我们将写一个测试,测试我们仓库中的create方法。

@Test
@DisplayName("Should create a file on a file system")
void givenUnixSystem_whenCreatingFile_thenCreatedInPath() {
    FileSystem fileSystem = Jimfs.newFileSystem(Configuration.unix());
    String fileName = "newFile.txt";
    Path pathToStore = fileSystem.getPath("");

    fileRepository.create(pathToStore, fileName);

    assertTrue(Files.exists(pathToStore.resolve(fileName)));
}

In this example, we’ve used the static method Jimfs.newFileSystem() to create a new in-memory file system. We pass a configuration object Configuration.unix(), which creates an immutable configuration for a Unix file system. This includes important OS-specific information such as path separators and information about symbolic links.

在本例中,我们使用static方法Jimfs.newFileSystem()来创建一个新的内存文件系统。我们传递了一个配置对象Configuration.unix(),它为 Unix 文件系统创建了一个不可变的配置。这包括重要的操作系统特定信息,如路径分隔符和符号链接的信息。

Now that we’ve created a file, we’re able to check if the file was created successfully on the Unix-based system.

现在我们已经创建了一个文件,我们能够检查该文件是否在基于Unix的系统上被成功创建。

4.2. Reading a File

4.2.读取文件

Next, we’ll test the method that reads the content of the file:

接下来,我们将测试读取文件内容的方法。

@Test
@DisplayName("Should read the content of the file")
void givenOSXSystem_whenReadingFile_thenContentIsReturned() throws Exception {
    FileSystem fileSystem = Jimfs.newFileSystem(Configuration.osX());
    Path resourceFilePath = fileSystem.getPath(RESOURCE_FILE_NAME);
    Files.copy(getResourceFilePath(), resourceFilePath);

    String content = fileRepository.read(resourceFilePath);

    assertEquals(FILE_CONTENT, content);
}

This time around, we’ve checked if it’s possible to read the content of the file on a macOS (formerly OSX) system by simply using a different type of configuration — Jimfs.newFileSystem(Configuration.osX()).

这一次,我们检查了是否有可能通过简单地使用不同类型的配置–Jimfs.newFileSystem(Configuration.osX())macOS(原OSX)系统上读取文件的内容。

4.3. Updating a File

4.3.更新一个文件

We can also use Jimfs to test the method that updates the content of the file:

我们还可以使用Jimfs来测试更新文件内容的方法。

@Test
@DisplayName("Should update the content of the file")
void givenWindowsSystem_whenUpdatingFile_thenContentHasChanged() throws Exception {
    FileSystem fileSystem = Jimfs.newFileSystem(Configuration.windows());
    Path resourceFilePath = fileSystem.getPath(RESOURCE_FILE_NAME);
    Files.copy(getResourceFilePath(), resourceFilePath);
    String newContent = "I'm updating you.";

    String content = fileRepository.update(resourceFilePath, newContent);

    assertEquals(newContent, content);
    assertEquals(newContent, fileRepository.read(resourceFilePath));
}

Likewise, this time we’ve checked how the method behaves on a Windows-based system by using Jimfs.newFileSystem(Configuration.windows()).

同样,这次我们通过使用Jimfs.newFileSystem(Configuration.windows()),检查了该方法在基于Windows的系统上的表现。

4.4. Deleting a File

4.4.删除一个文件

To conclude testing our CRUD operations, let’s test the method that deletes the file:

为了结束测试我们的CRUD操作,让我们测试一下删除文件的方法。

@Test
@DisplayName("Should delete file")
void givenCurrentSystem_whenDeletingFile_thenFileHasBeenDeleted() throws Exception {
    FileSystem fileSystem = Jimfs.newFileSystem();
    Path resourceFilePath = fileSystem.getPath(RESOURCE_FILE_NAME);
    Files.copy(getResourceFilePath(), resourceFilePath);

    fileRepository.delete(resourceFilePath);

    assertFalse(Files.exists(resourceFilePath));
}

Unlike previous examples, we’ve used Jimfs.newFileSystem() without specifying a file system configuration. In this case, Jimfs will create a new in-memory file system with a default configuration appropriate to the current operating system.

与之前的例子不同,我们使用了Jimfs.newFileSystem(),没有指定文件系统配置。在这种情况下,Jimfs 将创建一个新的内存文件系统,其默认配置适合于当前的操作系统。

5. Moving a File

5.移动一个文件

In this section, we’ll learn how to test a method that moves a file from one directory to another.

在本节中,我们将学习如何测试一个将文件从一个目录移动到另一个目录的方法。

Firstly, let’s implement the move method using the standard java.nio.file.File class:

首先,让我们使用标准的java.nio.file.File类实现move方法。

void move(Path origin, Path destination) {
    try {
        Files.createDirectories(destination);
        Files.move(origin, destination, StandardCopyOption.REPLACE_EXISTING);
    } catch (IOException ex) {
        throw new UncheckedIOException(ex);
    }
}

We’re going to use a parameterized test to ensure that this method works on several different file systems:

我们将使用一个参数化的测试来确保这个方法在几个不同的文件系统上工作。

private static Stream<Arguments> provideFileSystem() {
    return Stream.of(
            Arguments.of(Jimfs.newFileSystem(Configuration.unix())),
            Arguments.of(Jimfs.newFileSystem(Configuration.windows())),
            Arguments.of(Jimfs.newFileSystem(Configuration.osX())));
}

@ParameterizedTest
@DisplayName("Should move file to new destination")
@MethodSource("provideFileSystem")
void givenEachSystem_whenMovingFile_thenMovedToNewPath(FileSystem fileSystem) throws Exception {
    Path origin = fileSystem.getPath(RESOURCE_FILE_NAME);
    Files.copy(getResourceFilePath(), origin);
    Path destination = fileSystem.getPath("newDirectory", RESOURCE_FILE_NAME);

    fileManipulation.move(origin, destination);

    assertFalse(Files.exists(origin));
    assertTrue(Files.exists(destination));
}

As we can see, we’ve also been able to use Jimfs to test that we can move files on a variety of different file systems from a single unit test.

正如我们所看到的,我们也已经能够使用Jimfs来测试,我们可以从一个单元测试中移动各种不同文件系统上的文件。

6. Operating System Dependent Tests

6.取决于操作系统的测试

To demonstrate another benefit of using Jimfs, let’s create a FilePathReader class. The class is responsible for returning the real system path, which is, of course, OS-dependent:

为了证明使用Jimfs的另一个好处,我们来创建一个FilePathReader类。该类负责返回真实的系统路径,当然,这取决于操作系统。

class FilePathReader {

    String getSystemPath(Path path) {
        try {
            return path
              .toRealPath()
              .toString();
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }
}

Now, let’s add a test for this class:

现在,让我们为这个类添加一个测试。

class FilePathReaderUnitTest {

    private static String DIRECTORY_NAME = "baeldung";

    private FilePathReader filePathReader = new FilePathReader();

    @Test
    @DisplayName("Should get path on windows")
    void givenWindowsSystem_shouldGetPath_thenReturnWindowsPath() throws Exception {
        FileSystem fileSystem = Jimfs.newFileSystem(Configuration.windows());
        Path path = getPathToFile(fileSystem);

        String stringPath = filePathReader.getSystemPath(path);

        assertEquals("C:\\work\\" + DIRECTORY_NAME, stringPath);
    }

    @Test
    @DisplayName("Should get path on unix")
    void givenUnixSystem_shouldGetPath_thenReturnUnixPath() throws Exception {
        FileSystem fileSystem = Jimfs.newFileSystem(Configuration.unix());
        Path path = getPathToFile(fileSystem);

        String stringPath = filePathReader.getSystemPath(path);

        assertEquals("/work/" + DIRECTORY_NAME, stringPath);
    }

    private Path getPathToFile(FileSystem fileSystem) throws Exception {
        Path path = fileSystem.getPath(DIRECTORY_NAME);
        Files.createDirectory(path);

        return path;
    }
}

As we can see, the output for Windows differs from the one of Unix, as we’d expect. Moreover, we didn’t have to run these tests using two different file systems — Jimfs mocked it for us automatically.

正如我们所看到的,Windows的输出与Unix的输出不同,正如我们所期望的那样。此外,我们没有必要使用两个不同的文件系统来运行这些测试–Jimfs自动为我们模拟了

It’s worth mentioning that Jimfs doesn’t support the toFile() method that returns a java.io.File. It’s the only method from the Path class that isn’t supported. Therefore, it might be better to operate on an InputStream rather than a File.

值得一提的是,Jimfs不支持返回java.io.FiletoFile()方法。这是Path类中唯一不被支持的方法。因此,对InputStream而不是File进行操作可能更好。

7. Conclusion

7.结语

In this article, we’ve learned how to use use the in-memory file system Jimfs to mock file system interactions from our unit tests.

在这篇文章中,我们已经学会了如何使用内存文件系统Jimfs来模拟单元测试中的文件系统交互。

First, we started by defining a simple file repository with several CRUD operations. Then we saw examples of how to test each of the methods using a different file system type. Finally, we saw an example of how we can use Jimfs to test OS-dependent file system handling.

首先,我们开始定义一个简单的文件库,有几个CRUD操作。然后我们看到了如何使用不同的文件系统类型来测试每个方法的例子。最后,我们看到一个例子,说明我们如何使用Jimfs来测试依赖于操作系统的文件系统处理。

As always, the code for these examples is available over on Github.

像往常一样,这些例子的代码可在Github上获得