Vishal Dhasal
Vishal Dhasal

Reputation: 61

Mocking in Xunit- How to verify method that returns some value

I have created a unit test method that verifies method is called, below is the code for the same.The method builds email object and calls GeneratePDF method which returns bytes further BuildEmailInfo method returns email object.

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;
    }

Below is my unit test code , whenever I execute this it throws an error Expected invocation on the mock at least once, but was never performed: x=>x.GeneratePDF(pdfdata). Also Let me know if it is right way to perform the test

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));
    }
  }

Upvotes: 1

Views: 5894

Answers (2)

quetzalcoatl
quetzalcoatl

Reputation: 33586

This line:

        _mockPdfCreator.Verify(x => x.GeneratePDF(otp));

performs a 'verification'. This is an assertion that checks if method .GeneratePDF has been called on _mockPdfCreator with otp as its parameter.

All .Verify methods from the interface of the Mock object are used to check if some method or property were called. You can also provide some filters to see if certain parameters were passed, for example:

        _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));

all of these check if FooBar was called on _myMock, but each of them looks only at calls that used certain values of parameters: 5, 123, anything-that-is-int, or (...).

You cannot use .Verify to check for the return value.
There's no such option there.

Why? Think about it. You've had:

    _mockPdfCreator = new Mock<IPDFCreater>();
    ....
    _mockPdfCreator.Verify(x => x.GeneratePDF(otp));

The _mockPdfCreator is your mock object. Not a real thing. It's a tiny ghost that acts as if it were some IPDFCreater.

There's not a slightest bit of a real implementation there.
How can you expect that GeneratePDF returns anything meaningful?
It just won't. Nothing's there behind it. If anything called that method GeneratePDF, it would return NULL (or throw exception, depending on mocking mode: Loose/Strict).

...unless you SET UP your mock to do it differently:

    var theThing = ...;
    _mockPdfCreator = new Mock<IPDFCreater>();
    _mockPdfCreator.Setup(x => x.GeneratePDF(It.IsAny<...>())).Returns(theThing);
    ....
    // ... now check what `GeneratePDF` returned?!

Now of anything calls GeneratePDF method, it will return theThing. Alright.
But you knew that already. There's nothing to check. You set up GeneratePDF to return the thing, so there's not a slightest point to check what GeneratePDF returned. It's your mock and your setup!


Sooo, if anything called GeneratePDF, then NULL would be returned, because there's no setup for GeneratePDF. However, as Verify proved, the GeneratePDF was never called. This means that when you created the SMTPEmailSender giving it the mock as parameter:

    _mockPdfCreator = new Mock<IPDFCreater>();
    _sMTPEmailSender = new SMTPEmailSender(_mockPdfCreator.Object);

and then in test you've had:

    ....
    var result = _sMTPEmailSender.BuildEmailInfo(smpturl, faxMailDTO);
    _mockPdfCreator.Verify(x => x.GeneratePDF(otp));

then, apparently, the _sMTPEmailSender.BuildEmailInfo didn't fancy calling GeneratePDF at all.

Why? No idea. Most probably there was something either in smpturl or faxMailDTO that was considered invalid for this use case and that generate-pdf step was skipped. Check the Result. See if there are any errors or messages that would tell you anything about why it did not even try to call GeneratePDF.

Also note that the verification you wrote were

x => x.GeneratePDF(otp)

That's pretty specific. It has hard-coded reference to otp. So maybe it was called, but with different parameter value?

Try adding:

    var result = _sMTPEmailSender.BuildEmailInfo(smpturl, faxMailDTO);
    _mockPdfCreator.Verify(x => x.GeneratePDF(It.IsAny<PDFData>())); // <-
    _mockPdfCreator.Verify(x => x.GeneratePDF(otp));

or something along the lines and see which Verify fails. If the former passes and the latter fails, than everything's mostly fine, it's just not the exact OTP that you expected (maybe _sMTPEmailSender cloned it? etc).

In any chance that the former fails, then it means that GeneratePDF is truly not called even a single time, and then it means you have to learn why BuildEmailInfo with params (smpturl, faxMailDTO) doesn't do what you expect. You've got a try-catch-log there. Maybe some nullreferenceexption? But I doubt it.

You've got there:

[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

So, the faxMailDTO is from GetFaxAsMailObject. The BuildEmailInfo gets it via params and passes part of it to GeneratePDF. Then you assert in Verify that D uses newly-constructed otp from line C. That just can't work. The faxMailDTO from A+B so from GetFaxAsMailObject certainly DOES NOT contain otp from C and certainly will not pass otp object to GeneratePDF. The GeneratePDF will get some other PDFData object that came from faxMailDTO from A+B.

I think I've said enough and covered all issues with your test setup.. You almost have it right. Good luck!

Upvotes: 3

jeroenh
jeroenh

Reputation: 26792

Verifications on mock objects should be your 'last resort' in unit tests. Think about it: is an actual requirement violated if the PDF creator does not call the GeneratePDF method? The user only cares that the PDF was generated.

In this case, you can verify the outcome of the BuildEmailInfo method directly, for example:

var result =  _sMTPEmailSender.BuildEmailInfo(smpturl, faxMailDTO);
var expectedBytes = ...; // TODO - get the expected byte[] array from somewhere
Assert.Equal(expectedBytes, result.ContentStream.ToArray());

Additionally, you may be able to write this test without mocking the dependency at all? If the actual PDF creator object can be called to generate a byte[] array in memory, you could just use the real object instead of mocking it.

Upvotes: 0

Related Questions