vendredi 28 août 2015

How to mock Java Path API with Mockito

Java Path API is a better replacement of Java File API but massive usage of static methods makes it difficult to mock with Mockito. From my own class, I inject a FileSystem instance which I replace with a mock during unit tests.

However, I need to mock a lot of methods (and also creates a lot of mocks) to achieve this. And this is repeatly so many times across my test classes. So I start thinking about setup a simple API to register Path-s and declare associated behaviour.

For example, I need to check error handling on stream opening. The main class:

class MyClass {
    private FileSystem fileSystem;

    public MyClass(FileSystem fileSystem) {
        this.fileSystem = fileSystem;
    }

    public void operation() {
        String filename = /* such way to retrieve filename, ie database access */
        try (InputStream in = Files.newInputStream(fileSystem.getPath(filename))) {
            /* file content handling */
        } catch (IOException e) {
            /* business error management */
        }
    }
}

The test class:

 class MyClassTest {

     @Test
     public void operation_encounterIOException() {
         //Arrange
         MyClass instance = new MyClass(fileSystem);

         FileSystem fileSystem = mock(FileSystem.class);
         FileSystemProvider fileSystemProvider = mock(FileSystemProvider.class);
         Path path = mock(Path.class);
         doReturn(path).when(fileSystem).getPath("/dir/file.txt");
         doReturn(fileSystemProvider).when(path).provider();
         doThrow(new IOException("fileOperation_checkError")).when(fileSystemProvider).newInputStream(path, (OpenOption)anyVararg());

         //Act
         instance.operation();

         //Assert
         /* ... */
     }

     @Test
     public void operation_normalBehaviour() {
         //Arrange
         MyClass instance = new MyClass(fileSystem);

         FileSystem fileSystem = mock(FileSystem.class);
         FileSystemProvider fileSystemProvider = mock(FileSystemProvider.class);
         Path path = mock(Path.class);
         doReturn(path).when(fileSystem).getPath("/dir/file.txt");
         doReturn(fileSystemProvider).when(path).provider();
         ByteArrayInputStream in = new ByteArrayInputStream(/* arranged content */);
         doReturn(in).when(fileSystemProvider).newInputStream(path, (OpenOption)anyVararg());

         //Act
         instance.operation();

         //Assert
         /* ... */
     }
 }

I have many classes/tests of this kind and mock setup can be more tricky as static methods may call 3-6 non-static methods over the Path API. I have refactored test to avoid most redondant code but my simple API tends to be very limited as my Path API usage grown. So again it's time to refactor.

However, the logic I'm thinking about seems ugly and requires much code for a basic usage. The way I would like to ease API mocking (whatever is Java Path API or not) is based on the following principles:

  1. Creates abstract classes that implements inteface or extends class to mock.
  2. Implements methods that I don't want to mock.
  3. When invoking a "partial mock" I want to execute (in preference order) : explicitly mocked methods, implemented methods, default answer.

In order to achieve the third step, I think about creating an Answer which lookup for implemented method and fallback to a default answer. Then an instance of this Answer is passed at mock creation.

Are there existing ways to achieve this directly from Mockito or other ways to handle the problem ?

Aucun commentaire:

Enregistrer un commentaire