模拟接口不模拟
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);
我在一个项目中遇到过这样的方法,类似的简要逻辑如下
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
analyzerService.Setup(analyzer =>
analyzer.AnalyseExameFile(It.IsAny<ExamResultFile>()))
.Returns(ExamResultEnum.Pass);