[英]Mocking in Xunit- How to verify method that returns some value
我创建了一个单元测试方法来验证方法被调用,下面是相同的代码。该方法构建电子邮件对象并调用 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
上调用, _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));
所有这些都检查FooBar
是否在_myMock
,但它们中的每一个都只查看使用某些参数值的调用: _myMock
、 _myMock
-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
with params (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 使用 C 行新构造的otp
断言。那是行不通的。 来自 A+B 的faxMailDTO
所以来自GetFaxAsMailObject
肯定不包含来自 C 的otp
并且肯定不会将otp
对象传递给 GeneratePDF。 GeneratePDF 将获得来自 A+B 的faxMailDTO
一些其他 PDFData 对象。
我想我已经说得够多了,并且涵盖了您的测试设置的所有问题。您几乎说对了。 祝你好运!
对模拟对象的验证应该是您在单元测试中的“最后手段”。 想一想:如果 PDF 创建者不调用 GeneratePDF 方法,是否违反了实际要求? 用户只关心生成了 PDF。
在这种情况下,您可以直接验证 BuildEmailInfo 方法的结果,例如:
var result = _sMTPEmailSender.BuildEmailInfo(smpturl, faxMailDTO);
var expectedBytes = ...; // TODO - get the expected byte[] array from somewhere
Assert.Equal(expectedBytes, result.ContentStream.ToArray());
此外,您是否可以在完全不模拟依赖项的情况下编写此测试? 如果可以调用实际的 PDF 创建者对象以在内存中生成 byte[] 数组,则您可以只使用真实对象而不是模拟它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.