Jason Sultana

Follow this space for writings (and ramblings) about interesting things related to software development.

Re-usable mocks in .NET using Moq

05 Aug 2023 » dotnet, testing

G’day guys!

A while back I wrote a little example of using the Builder pattern in automated tests, and I wanted to follow it up with another common practice that I employ in test projects - re-usable mocks.

What are mocks again?

For those of you who might be new-ish to automated tests, imagine you’ve got a class that looks something like this:

public class Mailer
{
    private readonly ISmtpClient _smtpClient;

    public Mailer(ISmtpClient smtpClient)
    {
        _smtpClient = smtpClient;
    }

    public async Task SendMailAsync(SendMailRequest request)
    {
        await _smtpClient.SendAsync(new SmtpMessage()
        {
            Port = 25,
            To = request.To,
            Credentials = CredentialsFactory.GetSmtpCredentials(),
            // ...
        });
    }
}

Note: The above code is an imaginary sample. These classes don’t actually exist.

If we wanted to create an isolated test for this, that doesn’t actually send any emails (i.e - a unit test) then we need a way of creating a fake instance of ISmtpClient that we control, which doesn’t actually send any mail, but allows us to inspect when methods get called, provide dummy data, etc. That’s basically a mock.

Writing mocks with…Moq

If we then wanted to create a unit test (with mocks) for the above mailer class, it might looks something like this:

public class MailerTests
{
    [Fact]
    public async Task Can_send_mail()
    {
        // Arrange
        var smtp = new Mock<ISmtpClient>();
        smtp.Setup(m => m.SendAsync(It.IsAny<SmtpRequest>())).Verifiable();

        // Act
        var sut = new Mailer(smtp.Object);
        await sut.SendMailAsync(new SendMailRequest()
        {
            // 
        });

        // Assert
        smtp.Verify(); // this will throw if SendAsync was not called
    }
}

In short, we create a mock of ISmtpClient called smtp, set it up so that it mocks the SendAsync method and mark it as verifiable - so that we can verify if it was called later. We pass the mock into the Mailer (the service under test) and then verify later that the method was called.

Sounds good! What’s wrong with this?

I wouldn’t say that anything is wrong with it - but it can be made more reusable. Let’s just say that we had a couple of other tests (and potentially other services, which all have their own tests, too) that all use the ISmtpClient. We’d have to repeat this process of creating a mock every time. Whereas instead, we could do this:

public class MockSmtpClient : IMock<SmtpClient>
{
    public MockSmtpClient SetupSendAsync(bool veriable = false)
    {
        var setup = Setup(m => m.SendAsync(It.IsAny<SmtpRequest>()));

        if (verifiable)
            setup.Verifiable();

        return this;
    }
}

The idea is that now we have a re-usable mock that we can use across multiple tests. Especially when the service that you’re mocking contains a number of methods that will be used (think repositories and other data-related services), this can make the test code a lot more concise and readable. It also means that if you ever want to change how all your mocks are setup (eg: to make them all verifable), you should have far fewer places to change than trying to change every instance of Mock<T>.

Anyway - that about wraps me up. Does anyone follow a similar practice, or have any other common practices when it comes to automated tests? Let me know in the comments!

Catch ya!