模拟接口不模拟

Mocked interface doesn't mock

我在一个项目中遇到过这样的方法,类似的简要逻辑如下

public void BookAClass()
{
    _eventPublisher.Publish(new BookReadyEvent());
    //Using static class Reader to
    //read a configured Storage Place
    //to retrieve an ExamResultFile object
    var examResultFile = Reader.ReadFile("xxxx");
    var examResultLevel = _analyzer.AnalyseExameFile(examResultFile);

    if(examResultLevel == ExamResultEnum.Pass)
    {
        _eventPublisher.Publish(new BookCancelEvent());
    }
    else//examResultLevel == ExamResultEnum.Failed
    {
        _eventPublisher.Publish(new BookStartingEvent());
        //TODO
    }
}

并且我尝试针对两种情况实施单元测试:通过和失败。关于通过条件

[TestMethod()]
public void BookAClass_Pass_ExecuteAll()
{
    var analyzerService = new Mock<AnalyzerService>();
    analyzerService.Setup(analyzer =>
        analyzer.AnalyseExameFile(new ExamResultFile()))
        .Returns(ExamResultEnum.Pass);

    var student = new Student(analyzerService.Object);
    student.BookAClass();
}

毫无疑问,单元测试失败了,因为AnalyseExameFile会从Reader.ReadFile("xxxx")的响应中接受一个对象,而不是我的假数据,这导致examResultLevel每次都为null。因此,永远不会触发通过条件。

我知道如果将 examResultFile 视为输入参数会更好,而不是从 BookAClass 中调用的方法返回的对象。但是实际场景要复杂得多,所以我现在不能重构它。

请问有没有针对这种情况进行单元测试的解决方案?

是的,您可以只告诉 Moq 忽略您提供的参数(因此考虑为所有可能的参数完成设置)。

在最简单的情况下,您可以这样写:

analyzerService.Setup(analyzer =>
        analyzer.AnalyseExameFile(It.IsAny<ExamResultFile>()))
        .Returns(ExamResultEnum.Pass);

这声明此设置应用于 AnalyseExameFile 的任何调用,无论参数如何。在高级场景下,你还可以做

analyzerService.Setup(analyzer =>
        analyzer.AnalyseExameFile(It.Is<ExamResultFile>(x => x.Name == "Hans")))
        .Returns(ExamResultEnum.Pass);

这使用函子来定义参数是否应该匹配。

如果您将 mock 创建为 var analyzerService = new Mock<AnalyzerService>(MockBehavior.Strict);,您将被告知缺少呼叫设置(在您的情况下,该设置静默 returns null)。

您可以使用 It.IsAny 函数来确保匹配传递给 AnalyseExameFile

的任何参数,而不是传递新的 ExamResultFile 实例
analyzerService.Setup(analyzer =>
        analyzer.AnalyseExameFile(It.IsAny<ExamResultFile>()))
        .Returns(ExamResultEnum.Pass);