Skip to content
Advertisement

How to use dependency injection to mock out file system access?

I have a class that creates a new File object:

public class MyClass {
    public MyClass(String path) {
        this.file = new File(this.getFilePath(path)); // this should be replaced with an IFile
    }

    public String getFilePath(String path) {
        // do something with path and return computed value
    }
}

Now I want to mock out the dependency to File. So I created an interface IFile:

public interface IFile {
    public String[] list();
    public String getPath();
    // ...
}

For the actual code I created a FileWrapper class that implements this interface and calls java.io.File:

public class FileWrapper implements IFile {
    private File file;
    public FileWrapper(String path) {
        this.file = new File(path);
    }
    public String[] list() {
        return this.file.list();
    }
    public String getPath() {
        return this.file.getPath();
    }
}

My initial idea was to use IFile in the constructor of MyClass (and create a mock implementation for the tests), but this is not possible, because the IFile can only be created inside the MyClass class, as the argument of the File constructor depends on values of the class itself.

How can I dynamically set the class of the file attribute inside MyClass, so it depends on the interface IFile?

Advertisement

Answer

There are several solutions but the most flexible one I can think of the at the moment is to introduce a factory. There are many possible options here but some of them are:

  1. If you want to use it in unit tests where you have Mockito or Spock or anything like that you can pass mocked factory and return from it what you want. In this case you don’t even need those custom interfaces / classes like IFile / FileWrapper, you can mock File class directly if you use e.g. bytebuddy.

So it could look like this:

class FileFactory {

  File createFile(String path) {
    return new File(path);
  }
}

class MyClass {
  
  MyClass(FileFactory factory, String path) {
    this.file = factory.createFile(path);
  }
}

and in the unit test you just need to create mocked FileFactory and pass it as an argument to MyClass’s constructor.

  1. Alternatively if you don’t want to mock File class you can use your IFile interface along with FileWrapper implementation so the factory would look like this:
class FileFactory {

  IFile createFile(String path) {
    return new FileWrapper(path);
  }
}

but the other things look similar – you just need to create mocked factory in tests and pass it to MyClass’s constructor.

  1. If you use no framework/library, then you can implement mocks by yourself like this:
class MockFileFactory extends FileFactory {

  @Override
  IFile createFile(String path) {
    return new MockFile(path);
  }
}

class MockFile extends FileWrapper {
  // override existing methods and do what you like to do here
}

Alternatively, you could get rid of IFile interface and use File class instead. In tests you would need mocked version of it like this:

class MockFile extends File {
  // override existing methods and do what you like to do here
}
Advertisement