In-Memory Temp Folder in JUnit 5

„Die Erinnerung darf nicht enden; sie muss auch künftige Generationen zur Wachsamkeit mahnen. Es ist deshalb wichtig, nun eine Form des Erinnerns zu finden, die in die Zukunft wirkt.“

Bundespräsident Roman Herzog

While implementing some tests for the Freshmarker 1.0.0 release, I discovered another interesting possibility for a JUnit 5 TempDirFactory implementation. But first things first.

The JUnit 5 TempDirFactory is a mechanism to create your own temp directories for JUnit 5 tests. Normally, the @TempDir annotation is used to obtain a File or Path instance that refers to a system-specific temp directory.

@Test
void test(@TempDir Path folder) {
  Files.writeString(folder.resolve("test.txt", "Hello World");
  // ...
}

The test uses the temp directory folder to create a file in it. At the end of the test, the directory is usually deleted automatically.

In the article Custom Temp Folders in JUnit 5, a custom TempDirFactory was implemented that created temp directories in the Maven target directory. However, the new TemplDirFactory implementation should not use a different directory, but a different file system.

Before Java 7, a file instance was directly assigned to a file or a directory in the native file system. Since Java 7, it is possible to use other file systems in addition to the standard file system. With the introduction of Java NIO, developers can use Path instances to access files that are not located in the native file system, but somewhere else.

Unfortunately, there are very few implementations of a FileSystem outside of the standard libraries. In addition to the standard FileSystem implementations for the native file systems of the various operating systems, there is also the ZipFileSystem, which can be used to open an archive file as a separate file system.

A well-known Java file system outside the standard libraries is Jimfs from Google. It is an in-memory file system that keeps all data, unsurprisingly, in memory. The advantage of such a file system is its speed, because no hard disc accesses are required. The disadvantage is that no data is persisted, it only exists during the runtime of the application.

To work with Jimfs, we first need the library as a dependency in our project.

<dependency>
  <groupId>com.google.jimfs</groupId>
  <artifactId>jimfs</artifactId>
  <version>${jimfs.version}</version>
</dependency>

A custom TempDirFactory that uses the Jimfs is very easy to implement.

public class InMemoryTempDirFactory implements TempDirFactory {
    private final Path root;

    public InMemoryTempDirFactory() {
      FileSystem fileSystem = Jimfs.newFileSystem(com.google.common.jimfs.Configuration.unix());
      root = fileSystem.getPath("/");
    }
    
    @Override
    public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws Exception {
        return Files.createTempDirectory(root, "junit");
    }
}

A Jimfs file system and a path instance for the root directory are created in the constructor. In the createTempDirectory, a temp directory with the prefix junit is then always created in the root directory.

With a small change, our example unit test no longer writes the file to the native file system but to an in-memory file system.

@Test
void test(@TempDir(factory=de.schegge.test.InMemoryTempDirFactory.class) Path folder) {
  Files.writeString(folder.resolve("test.txt", "Hello World");
  // ...
}

The only necessary change is the factory attribute with our own InMemoryTempDirFactory at the @TempDir annotation.

If you would like to try out the InMemoryTempDirFactory for yourself, you can find it as part of the junit-5-gimmicks on Maven Central.

<dependency>
    <groupId>de.schegge</groupId>
    <artifactId>junit-5-gimmicks</artifactId>
    <version>0.2.0</version>
</dependency>

Leave a Comment