c# - 在 Xunit 中模拟-如何验证返回某些值的方法
问题描述
我创建了一个单元测试方法来验证方法是否被调用,下面是相同的代码。该方法构建电子邮件对象并调用 GeneratePDF 方法,该方法进一步返回字节 BuildEmailInfo 方法返回电子邮件对象。
public class SMTPEmailSender : IEmailSender
{
private IPDFCreater _pdfCreater;
public SMTPEmailSender(IPDFCreater pdfCreater)
{
_pdfCreater = pdfCreater;
}
public Email BuildEmailInfo(string sMTPServerUrl, FaxMailDTO faxAsMailRequest)
{
Email email=null;
try
{
var otp = new PDFData { OTP =faxAsMailRequest.OTP};
email = new Email
{
SMTPServerUrl = sMTPServerUrl,
Encoding = Encoding.UTF8,
ToAddress = faxAsMailRequest.ToEmailAddress,
ToAddressDisplayName = faxAsMailRequest.ToAddressDisplayName,
FromAddress = faxAsMailRequest.FromEmailAddress,
Subject = faxAsMailRequest.Subject,
Body = faxAsMailRequest.Body,
FromAddressDisplayName = faxAsMailRequest.FromAddressDisplayName,
ContentStream = new MemoryStream(_pdfCreater.GeneratePDF(otp)),
AttachmentName = faxAsMailRequest.FaxFileName
};
}
catch(Exception ex)
{
Log.Error("Method : BuildEmailInfo. Exception raised while building email data : {@Message}", ex.Message, ex);
}
return email;
}
下面是我的单元测试代码,每当我执行此代码时,它都会在模拟上至少抛出一次错误预期调用,但从未执行过:x=>x.GeneratePDF(pdfdata)。也让我知道执行测试的方法是否正确
public class SMTPEmailSenderTest
{
private SMTPEmailSender _sMTPEmailSender;
Mock<IPDFCreater> _mockPdfCreator;
public SMTPEmailSenderTest()
{
_mockPdfCreator = new Mock<IPDFCreater>();
_sMTPEmailSender = new SMTPEmailSender(_mockPdfCreator.Object);
}
[Theory]
[MemberData(nameof(GetFaxAsMailObject))]
public void BuildEmailInfoTest_ReturnsValidEmailObject(FaxMailDTO faxMailDTO)
{
string smpturl = "localhost";
var otp = new PDFData { OTP = faxMailDTO.OTP };
var result = _sMTPEmailSender.BuildEmailInfo(smpturl, faxMailDTO);
_mockPdfCreator.Verify(x => x.GeneratePDF(otp));
}
}
解决方案
这一行:
_mockPdfCreator.Verify(x => x.GeneratePDF(otp));
执行“验证”。这是一个断言,用于检查方法.GeneratePDF
是否已作为其参数被_mockPdfCreator
调用otp
。
Mock 对象的接口中的所有 .Verify 方法都用于检查是否调用了某些方法或属性。您还可以提供一些过滤器来查看是否传递了某些参数,例如:
_myMock.Verify(x => x.FooBar(5));
_myMock.Verify(x => x.FooBar(123));
_myMock.Verify(x => x.FooBar(It.IsAny<int>());
_myMock.Verify(x => x.FooBar(It.Is<int>(number => (number-5)%3 > 10));
所有这些都检查 if 是否FooBar
被引用_myMock
,但它们中的每一个都只查看使用某些参数值的调用:5、123、anything-that-is-int 或 (...)。
您不能使用 .Verify 来检查返回值。
那里没有这样的选择。
为什么?想想看。你有过:
_mockPdfCreator = new Mock<IPDFCreater>();
....
_mockPdfCreator.Verify(x => x.GeneratePDF(otp));
是你的_mockPdfCreator
模拟对象。不是真实的东西。它是一个小幽灵,就好像它是某个 IPDFCreater。
那里没有一点真正的实现。
你怎么能期望GeneratePDF
返回任何有意义的东西?
它只是不会。后面什么都没有。如果有任何方法调用该方法GeneratePDF
,它将返回 NULL(或抛出异常,取决于模拟模式:松散/严格)。
...除非你设置你的模拟来做不同的事情:
var theThing = ...;
_mockPdfCreator = new Mock<IPDFCreater>();
_mockPdfCreator.Setup(x => x.GeneratePDF(It.IsAny<...>())).Returns(theThing);
....
// ... now check what `GeneratePDF` returned?!
现在任何调用GeneratePDF
方法的东西都会返回theThing
。好吧。
但是你已经知道了。没有什么要检查的。您将 GeneratePDF 设置为返回该内容,因此无需检查 GeneratePDF 返回的内容。这是你的模拟和你的设置!
Sooo,如果有任何称为 GeneratePDF,则将返回 NULL,因为 GeneratePDF 没有设置。然而,正如验证所证明的那样,GeneratePDF 从未被调用过。这意味着当您创建 SMTPEmailSender 时,将模拟作为参数:
_mockPdfCreator = new Mock<IPDFCreater>();
_sMTPEmailSender = new SMTPEmailSender(_mockPdfCreator.Object);
然后在测试中你有:
....
var result = _sMTPEmailSender.BuildEmailInfo(smpturl, faxMailDTO);
_mockPdfCreator.Verify(x => x.GeneratePDF(otp));
然后,显然,_sMTPEmailSender.BuildEmailInfo
根本不想打电话GeneratePDF
。
为什么?不知道。很可能有一些东西在这个用例中smpturl
或者faxMailDTO
被认为是无效的,并且跳过了 generate-pdf 步骤。检查结果。查看是否有任何错误或消息可以告诉您为什么它甚至没有尝试调用 GeneratePDF。
另请注意,您编写的验证是
x => x.GeneratePDF(otp)
这很具体。它具有对otp
. 所以也许它被调用了,但是参数值不同?
尝试添加:
var result = _sMTPEmailSender.BuildEmailInfo(smpturl, faxMailDTO);
_mockPdfCreator.Verify(x => x.GeneratePDF(It.IsAny<PDFData>())); // <-
_mockPdfCreator.Verify(x => x.GeneratePDF(otp));
或类似的东西,看看哪个验证失败。如果前者通过而后者失败,那么一切都很好,这不是您期望的确切 OTP(也许 _sMTPEmailSender 克隆了它?等等)。
如果前者失败,那么这意味着它GeneratePDF
真的没有被调用一次,然后它意味着你必须了解为什么BuildEmailInfo
使用参数(smpturl,faxMailDTO)不能达到你的预期。你有一个 try-catch-log 那里。也许一些nullreferenceexption?但我对此表示怀疑。
你已经到了:
[MemberData(nameof(GetFaxAsMailObject))] /// <==== B
public void BuildEmailInfoTest_ReturnsValidEmailObject(FaxMailDTO faxMailDTO) // <--- A
{
...
var otp = new PDFData { OTP = faxMailDTO.OTP }; //<--- C
...
_mockPdfCreator.Verify(x => x.GeneratePDF(otp)); //<---D
因此,faxMailDTO
来自 GetFaxAsMailObject。BuildEmailInfo 通过 params 获取它并将其中的一部分传递给 GeneratePDF。然后你在验证中断言 D 使用otp
从 C 行新构建的。那是行不通的。faxMailDTO
from A+B 所以 fromGetFaxAsMailObject
当然不包含otp
from C 并且肯定不会将对象传递otp
给 GeneratePDF。GeneratePDF 将获得faxMailDTO
来自 A+B 的其他一些 PDFData 对象。
我想我已经说得够多了,并涵盖了你的测试设置的所有问题。你几乎是对的。祝你好运!
推荐阅读
- python - 用 Pandas 合并两个图形
- xcode - 自定义字体适用于情节提要,但不适用于设备和模拟器?
- javascript - 如何使用 JavaScript 用逗号格式化数字?
- excel - 使用 MVC .net Web 应用程序将 Excel 工作表导入数据库
- javascript - React.createElement - 将 jsx 大规模转换为普通函数调用
- python-3.x - 如何在 python 并行处理中在集群内创建集群?
- linux - 执行二进制文件时是否有任何工具可以控制线程数量?
- python - 在没有 xcode 或 mac 的情况下练习 lldb python 编程
- iframe - 如何使用 Safari App Extensions 将 iframe 正确注入 DOM?
- docker - 为什么我的 Docker 容器的可写层在容器退出后仍然存在?