Integration Testing with Jimfs virtual Filesystems

In various test cases, primarily those covering components that interact with the filesystem and use filesystem entitites for a convention-based operation, it perfectly makes sense to provide a filesystem resource in an expected state to assess the component-under-test’s behavior.

To provide a resource in an expected state, the classic approaches are:

  • Have a handcrafted directory with testdata on the developer machine
  • Couple the testdata with the source in the repository

Both methods are quite poor, as the first will definitively yield into trouble when the application gets built on a CI or any other machine, or when the fact that the local filesystem is anything but immutable shows it evil face. The second includes hand-crafted static data that has to be kept in sync with the application contracts and logic. Despite this might have been an acceptable approach in the 90s, please do not do this today.

More contemporary approaches are:

  • Have a configurable location on a ramfs/tmpfs with freshly prepared testdata on each @Before* part of the test.
  • In Spring Framework, use the TemporaryFolder resource, which promises to cleanup the resources after testing.

The above are quite decent, but it still depends on local, platform specific and non-reproduceable resources, a fact that may (and will) corrupt the actual test. So, why not use a layer which emulates a filesystem on java.nio layer and uses a throwaway, in-memory filesystem which gets assembled in an expected state on each test run?

Jimfs

Jimfs performs the task of providing a virtual in-memory filesystem which behaves more or less exactly as the DefaultFs. Being developed by google and quite feature-complete, it drives all my tests which require File or Path dependencies.

Example code

First, jimfs needs to be imported. For Maven, the import is:

A minimal test with Jimfs in JUnit4:

From here on, the jimfs filesystem behaves like the normal filesystem, so lets populate it with some testdata.

There is one single trap: According to the jimfs repository and the Java documentation, the Path class does not neccessarily have to provide a toFile() method – so if any method which ends up using a Path hosted by Jimfs, it should create an InputStream from the Path, which might also be a free lunch if the filesystem-centric code relies on Java8 Files.walk() functionality.

Memory filesystems in tests may be a bad smell

In Java8, there are a lot of ways to abstract filesystem-driven components from the actual storage. When the testing strategy requires the use of an in-memory filesystem, it might be a sign that a component is coupled with the filesystem too tightly.

My usual way to abstract the filesystem from my other code is using a Supplier<ContractType>, which can easily be mocked or stubbed for components which depend on it, while the supplier, as a last outpost before the filesystem, is the right component to be tested against a virtual filesystem.

So, memory filesystems may be a bad smell used in the wrong place, but really useful when used right.

dreese.de v8 (c)1994-2018 Manfred Dreese