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 guarantees to cleanup the resources after testing.
The above are quite decent, but they still have one mayor flaw: We should already feel guilty for providing a resource which requires to be unit/integration-tested against filesystem dependencies and do not practice loose coupling all the way, such as using a Java8 Provider interface instead of interacting with the filesystem directly. Besides, it requires additional configuration on the build machine. So, we should at least choose a method which uses the Java File and Path interfaces without relying on an actual filesystem and paying more attention to our testdata store as necessary. 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:
1<dependency>
2 <groupId>com.google.jimfs</groupId>
3 <artifactId>jimfs</artifactId>
4 <version>1.1</version>
5 <scope>test</scope>
6</dependency>
A minimal test with Jimfs in JUnit4:
1public class FilesystemDependingSubjectTest {
2
3 FileSystem memfs;
4 FilesystemDependingSubject subject;
5
6 @Before
7 public void setUp() throws IOException {
8 memfs = Jimfs.newFileSystem(Configuration.unix());
9 prepareMemfs(memfs, true, true);
10 }
11}
From here on, the jimfs filesystem behaves like the normal filesystem, so lets populate it with some testdata.
1Arrays.asList("/brandings",
2 "/a/b",
3 "/a/b/d",
4 "/a/b/e",
5 "/a/c",
6 "/a/c/e")
7.stream()
8.map(s -> fs.getPath(s))
9.forEach(path -> {
10 Path createDirectory = Files.createDirectory(p);
11});
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.
InputStream is = Files.newInputStream(path);