Using moq to test class that extends abstract class

Sometimes we've to test a class that extend an anstract class and use other components of the project. For example, we have a ExcelLoader that extend an abstract class Loader, that read an excel file and use data inserted in the file to send mail or send SMS to some users.

ExcelLoader It's an important class of our project so it's usefull cover this class with some test, but could be difficult test the class with really instance of the components that send mail and SMS.

So we want test only the behavior of ExcelLoader

The code

public abstract class Loader
{
    public abstract void ImportFile(Stream stream);
    public abstract IEnumerable<RowFile> GetRows(Stream stream);
}

public class ExcelLoader : Loader
{
    IMailManager mailManager;
    ISMSManager smsManager;
    public ExcelLoader(IMailManager mailManager, ISMSManager smsManager)
    {
        this.mailManager = mailManager;
        this.smsManager = smsManager;
    }

    public override void ImportFile(Stream stream)
    {
        var rows = GetRows(stream);
        foreach (var row in rows)
        {
            if (row.SendMail)
                mailManager.SendMail(row.Subject, row.Text, row.Mail);

             if (row.SendSMS)
                smsManager.SendSMS(row.PhoneNumber, row.Text);
        }
    }

    public override IEnumerable<rowfile> GetRows(Stream stream)
    {
        List<rowfile> response = new List<rowfile>();
        var wb = new XLWorkbook(stream);
        var ws = wb.Worksheet(0);
        foreach (var row in ws.Rows())
        {
            response.Add(new RowFile { 
                Text = row.Cell(0).Value.ToString(),
                Mail = row.Cell(1).Value.ToString(),
                Subject = row.Cell(2).Value.ToString(),
                SendMail = Convert.ToBoolean(row.Cell(3).Value)
            });
        }
        return response;
    }
}

Test the code with xunit

[Fact]
public void ImportResultAreBuildCorrectly()
{
    Mock<imailmanager> mailManager = new Mock<imailmanager>();
    Mock<ismsmanager> smsManager = new Mock<ismsmanager>();

    Mock<excelloader> mockImportResult 
        = new Mock<excelloader>(mailManager.Object, smsManager.Object);

    RowFile rowFile1 = new RowFile 
    { 
        Mail = "Luke.mail@gs.it", 
        Subject = "Subject mail Luke", 
        Text = "Text mail for Luke", 
        PhoneNumber = "", 
        SendMail = true, SendSMS = false 
    };

    RowFile rowFile2 = new RowFile 
    { 
        Mail = "", 
        Subject = "", 
        Text = "Text mail for Tom", 
        PhoneNumber = "123456",
        SendMail = false, 
        SendSMS = true 
    };

    mockImportResult.Setup(x => x.GetRows(It.IsAny<stream>()))
        .Returns(new List<rowfile> { rowFile1, rowFile2 } );

    mockImportResult.CallBase = true;

    mockImportResult.Object.ImportFile(It.IsAny<stream>());

    mailManager.Verify(x => x.SendMail(rowFile1.Subject, rowFile1.Text, rowFile1.Mail),
        Times.Once());
    mailManager.Verify(x => x.SendMail(rowFile2.Subject, rowFile2.Text, rowFile2.Mail),
        Times.Never());

    smsManager.Verify(x => x.SendSMS(rowFile1.PhoneNumber, rowFile1.Text),
        Times.Never());
    smsManager.Verify(x => x.SendSMS(rowFile2.PhoneNumber, rowFile2.Text), 
        Times.Once());
}

With this line of code

Mock<excelloader> mockImportResult 
    = new Mock<excelloader>(mailManager.Object, smsManager.Object);

We use mock to make a new instance of ExcelLoader, and add two Mock object as parameters of the constructor, Mail Manager and SMSManager.

We don't want test GetRows, because there are another test doing this task. We want build a test focused to ImportFile method. So we Mock the method GetRows.

mockImportResult.Setup(x => x.GetRows(It.IsAny<stream>()))
    .Returns(new List<rowfile> { rowFile1, rowFile2 } );

With this line of code

mockImportResult.CallBase = true;

We ask to Moq to use the methods that are implemented by ExcelLoader. Without this code, Moq try to use the method of abstract class, and when we run the test, ImportFile of ExcelLoader will not be called.

Than we can test with Verify if there are call to methods of SmsManager and MailManager. With verify, you can check if a call is make one time, exactly n times or never.

mailManager.Verify(x => x.SendMail(rowFile1.Subject, rowFile1.Text, rowFile1.Mail),
    Times.Once());
mailManager.Verify(x => x.SendMail(rowFile2.Subject, rowFile2.Text, rowFile2.Mail),
    Times.Never());

There are other methods, AtLeastOneTime, AtLeast n times, AtMostOnetime or AtMost n times.

Comments

Popular posts from this blog

Validate Automapper configurations with AssertConfigurationIsValid

The power of SpecFlow