如何为 System.IO.FileInfo 类或任何没有接口的类设置最小订购量(MOQ)?

30

我正在为我创建的记录器类编写一系列单元测试,并且我想模拟文件类。 我找不到需要使用以创建MOQ的接口……那么,如果没有接口,如何成功地MOQ一个类?

如果没有可用的接口,我也不清楚如何使用依赖注入:

private FileInfo _logFile;

public LogEventProcessorTextFile(FileInfo logFile) {
    _logFile = logFile;
}

当我真正想要做这样的事情时(注意我使用的是IFileInfo而不是FileInfo):

private IFileInfo _logFile;

public LogEventProcessorTextFile(IFileInfo logFile) {
    _logFile = logFile;
}

需要模拟文件类吗?它是静态的,不是吗? - tuinstoel
抱歉,那是一个打错的字...现在已经更正为FileInfo了。 - InvertedAcceleration
5个回答

35

设计你的代码,以便不直接访问FileInfo类,而是访问一个具有相同功能的接口(例如命名为IFileInfo)。在生产代码中,您将使用一个类,它只将所有功能委托给系统FileInfo类,但在单元测试中,您可以模拟该接口。

例如,在我制作的一个根据当前日期表现不同的应用程序中,我声明了以下接口:

interface IDateTimeProvider
{
    DateTime Today();
}

生产类别仅仅是:

class DateTimeProvider : IDateTimeProvider
{
    public DateTime Today()
    {
        return DateTime.Today;
    }
}

你可以辅助使用依赖注入引擎来决定在每种情况下应该使用真实类或模拟类。


1
我绝对百分之百赞成使用接口和依赖注入的想法,但我的问题是找不到FileInfo的接口...你是说我应该创建一个匹配我的FileInfo用法的接口吗?如果是这样,我如何使FileInfo“继承”我的接口(抱歉如果这个术语用于我所问的内容不正确)而不必访问它的源代码? - InvertedAcceleration
4
你不应该让FileInfo继承你的新接口,而是应该在自己实现接口的类中包装FileInfo。要了解更多信息,请参阅此页面上的“包装无法模拟的基础设施”部分:http://www.lostechies.com/blogs/gabrielschenker/archive/2009/02/27/refactoring-legacy-code.aspx - Pedro
1
非常感谢您的回答...它帮助我理解了“网关模式”,这显然是处理我问题的方法。如果您不熟悉wcoenen指向我的SystemWrapper库...您一定要去看看...结果正是我所需要的。但还是感谢您的建议,对我来说非常有价值。 - InvertedAcceleration

20

使用SystemWrapper库,它为许多未实现接口的 .NET 类提供了接口和可模拟的包装类。


7
为什么要使用额外的第三方库来进行单元测试? - Usama Khalil

4
在您的 csproj 文件中添加以下 NuGet 包:
<ItemGroup>
  <PackageReference Include="System.IO.Abstractions" Version="13.2.29" />
</ItemGroup>

System.IO.Abstractions.FileSystem 注入到你的类中:
  public class FileLogger
  {
    private readonly IFileSystem fileSystem;

    public FileLogger(System.IO.Abstractions.IFileSystem fileSystem) // new FileSystem()
    {
      this.fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
    }
    
    public void LogEventProcessorTextFile(string filePath)
    {
      IFileInfo fileInfo = fileSystem.FileInfo.FromFileName(filePath);

      LogEventProcessorTextFile(fileInfo);
    }

    private System.IO.Abstractions.IFileInfo _logFile;

    public void LogEventProcessorTextFile(IFileInfo logFile) {
      _logFile = logFile;
    }
  }

Moq示例:
private Mock<IFileSystem> FileSystemMock { get; set; }

[SetUp]
public void Setup()
{
  var file = Mock.Of<IFileInfo>();
  var directoryInfo = Mock.Of<IDirectoryInfo>();

  Mock.Get(file).Setup(c => c.Exists).Returns(true);

  FileSystemMock = new Mock<IFileSystem>();

  FileSystemMock.Setup(c => c.FileInfo.FromFileName(It.IsAny<string>())).Returns(file);
  FileSystemMock.Setup(c => c.DirectoryInfo.FromDirectoryName("Settings")).Returns(directoryInfo);
}

2

0

这种方法相对局限,因为你只能使用精确的文件,但它也很好,因为你可以完全控制单元测试中你文件里的内容... 你可以在单元测试项目中创建一个文件夹来保存虚拟日志文件。然后,在单元测试中使用以下命令获取当前目录:

var currentDirectory = Environment.CurrentDirectory

然后进入您的测试文件夹并使用以下命令获取测试文件:

var testFileInfos = Directory.GetFiles(
    Path.Combine(currentDirectory, "testFolderName"));

然后在您的测试中根据需要使用这些 FileInfo 对象。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接