簡體   English   中英

在 Xunit 中模擬 - 如何驗證返回某些值的方法

[英]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

為什么? 不知道。 很可能在smpturlfaxMailDTO中有一些東西被認為對這個用例無效,並且跳過了 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM