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:
- 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.
- 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.
- 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 }