如何使用依赖注入来模拟文件系统访问?

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

我有一个 class 创建新的 File 对象:

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

现在我想模拟出对 File 的依赖。所以我创建了一个接口 IFile:

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

对于实际代码,我创建了一个 FileWrapper class 来实现此接口并调用 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();
    }
}

我最初的想法是在 MyClass 的构造函数中使用 IFile(并为测试创建模拟实现),但这是不可能的,因为 IFile 可以仅在 MyClass class 内部创建,因为 File 构造函数的参数取决于 class 本身的值。

如何在MyClass里面动态设置file属性的class,所以要看接口IFile

有多种解决方案,但我目前能想到的最灵活的一种是引入工厂。这里有许多可能的选项,但其中一些是:

  1. 如果你想在有 Mockito 或 Spock 或类似东西的单元测试中使用它,你可以传递模拟工厂和 return 从它你想要什么。在这种情况下,您甚至不需要像 IFile / FileWrapper 这样的自定义接口 / class,如果您使用例如,您可以直接模拟文件 class字节好友.

所以它可能看起来像这样:

class FileFactory {

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

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

在单元测试中,您只需创建模拟的 FileFactory 并将其作为参数传递给 MyClass 的构造函数。

  1. 或者,如果您不想模拟 File class,您可以使用 IFile 接口和 FileWrapper 实现,这样工厂看起来像这样:
class FileFactory {

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

但其他事情看起来相似 - 您只需要在测试中创建模拟工厂并将其传递给 MyClass 的构造函数。

  1. 如果你不使用 framework/library,那么你可以像这样自己实现模拟:
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
}

或者,您可以摆脱 IFile 接口并改用 File class。在测试中,您需要这样的模拟版本:

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