Mocking du système de fichiers avec Jimfs

1. Vue d'ensemble

En règle générale, lorsque vous testez des composants qui utilisent fortement les opérations d'E / S, nos tests peuvent souffrir de plusieurs problèmes tels que des performances médiocres, une dépendance de plate-forme et un état inattendu.

Dans ce didacticiel, nous verrons comment nous pouvons atténuer ces problèmes en utilisant le système de fichiers en mémoire Jimfs.

2. Introduction à Jimfs

Jimfs est un système de fichiers en mémoire qui implémente l'API Java NIO et prend en charge presque toutes ses fonctionnalités. Ceci est particulièrement utile, car cela signifie que nous pouvons émuler un système de fichiers virtuel en mémoire et interagir avec lui en utilisant notre couche java.nio existante .

Comme nous allons le voir, il peut être avantageux d'utiliser un système de fichiers simulé au lieu d'un vrai pour:

  • Évitez d'être dépendant du système de fichiers qui exécute actuellement le test
  • Assurez-vous que le système de fichiers est assemblé avec l'état attendu à chaque exécution de test
  • Aidez à accélérer nos tests

Étant donné que les systèmes de fichiers varient considérablement, l'utilisation de Jimfs facilite également les tests avec des systèmes de fichiers de différents systèmes d'exploitation.

3. Dépendances de Maven

Tout d'abord, ajoutons les dépendances de projet dont nous aurons besoin pour nos exemples:

 com.google.jimfs jimfs 1.1 

La dépendance jimfs contient tout ce dont nous avons besoin pour utiliser notre système de fichiers simulé. De plus, nous allons écrire des tests en utilisant JUnit5.

4. Un référentiel de fichiers simple

Nous commencerons par définir une classe FileRepository simple qui implémente certaines opérations CRUD standard:

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); } } }

Comme nous pouvons le voir, chaque méthode utilise des classes java.nio standard .

4.1. Créer un fichier

Dans cette section, nous allons écrire un test qui teste la méthode create à partir de notre référentiel:

@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))); }

Dans cet exemple, nous avons utilisé la méthode statique Jimfs.newFileSystem () pour créer un nouveau système de fichiers en mémoire. Nous passons un objet de configuration Configuration.unix () , qui crée une configuration immuable pour un système de fichiers Unix . Cela inclut des informations importantes spécifiques au système d'exploitation telles que les séparateurs de chemin et des informations sur les liens symboliques.

Maintenant que nous avons créé un fichier, nous sommes en mesure de vérifier si le fichier a été créé avec succès sur le système Unix.

4.2. Lire un fichier

Ensuite, nous allons tester la méthode qui lit le contenu du fichier:

@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); }

Cette fois-ci, nous avons vérifié s'il est possible de lire le contenu du fichier sur un système macOS (anciennement OSX) en utilisant simplement un autre type de configuration - Jimfs.newFileSystem (Configuration.osX ()) .

4.3. Mettre à jour un fichier

Nous pouvons également utiliser Jimfs pour tester la méthode qui met à jour le contenu du fichier:

@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)); }

De même, cette fois, nous avons vérifié le comportement de la méthode sur un système Windows en utilisant Jimfs.newFileSystem (Configuration.windows ()) .

4.4. Supprimer un fichier

Pour terminer le test de nos opérations CRUD, testons la méthode qui supprime le fichier:

@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)); }

Contrairement aux exemples précédents, nous avons utilisé Jimfs.newFileSystem () sans spécifier de configuration de système de fichiers. Dans ce cas, Jimfs créera un nouveau système de fichiers en mémoire avec une configuration par défaut appropriée au système d'exploitation actuel.

5. Déplacement d'un fichier

Dans cette section, nous allons apprendre à tester une méthode qui déplace un fichier d'un répertoire à un autre.

Tout d'abord, implémentons la méthode move en utilisant la classe standard java.nio.file.File :

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

Nous allons utiliser un test paramétré pour nous assurer que cette méthode fonctionne sur plusieurs systèmes de fichiers différents:

private static Stream 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)); }

Comme nous pouvons le voir, nous avons également pu utiliser Jimfs pour tester que nous pouvons déplacer des fichiers sur une variété de systèmes de fichiers différents à partir d'un seul test unitaire.

6. Tests dépendants du système d'exploitation

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:

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.

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.

7. Conclusion

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.

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.

Comme toujours, le code de ces exemples est disponible à l'adresse over sur Github.