Stub vs Mock – How to Make an Intelligent Choice in C#?


One of the most important design principles of unit testing is testing in isolation.

That means that the code being tested should be isolated from all other objects so that you can test it in a clean environment. This makes sure that you only test one unit of work at a time. Therefore, you do not create side effects by testing too many classes at once. And for isolating classes you can use stubs or mocks.

So you might ask: what is the difference between a stub and a mock?

A stub is a fake object that does not affect the outcome of the test. His only purpose is to set up the test scenario by returning dummy test data.
A mock verifies that the unit under test behaves as expected. A test’s outcome depends on the state of the mock object after executing an action.

Mock and a stub are test doubles, so let’s dive deeper and explain what a test double is.

Taking a step back, what is a test double?

A test double is a replacement for a real object in a program’s system under test (SUT). The purpose of using a test double is to isolate the SUT and its collaborators from the outside world during unit testing.

This is important because using a real collaborator can make it difficult to test a class in isolation from its environment.  Test doubles are used in order to eliminate outside distractions and allow a programmer to fully focus on the SUT and the expected behavior of its methods.

Why should you use stubs and mocks?

Unit tests are one of the cornerstones of modern software development. In unit testing, you write code that tests other code. You can write unit tests for new code or write them for existing code to make sure it works.

The purpose of mocking and stubbing is to isolate your unit from its dependencies, allowing you to test your unit in a controlled environment.

unit testing

If your unit has hard dependencies, it can be difficult to test, and you will often need to mock or stub its dependencies. Mocking and stubbing are often used interchangeably, but they are not quite the same.

Mocking is the process of emulating the behavior of a real object, stubbing is the process of substituting a real object with a different object that has limited behaviors. Mocking and stubbing are not essential to unit testing, but when used correctly can make writing unit tests easier.

stub vs mock - comparison

Designing tests for a system can be a complex task, and many programmers’ first instinct is to have a fully working implementation of the system under test. However, this approach can be problematic. If the production code is complex, the tests can be long, and the implementation can be fragile. This is where mocking and stubbing come in. Mocking and stubbing allow us to create a simpler version of the system under test that we can use while writing tests.

Typical targets for mocking are:

  • Classes that interact with the database
  • Classes that perform network requests,
  • Slow classes, and
  • Classes that have side effects.

For example, think of a C# class that communicates with an external payment provider. There is no need to connect to the payment provider each time the unit test runs. It does not make sense to test the code that charges credit cards using a live payment environment. It would also make the unit test non-deterministic, for example, if the payment provider is down for some reason. Or if the network request took too long to finish.

You want your unit tests to be quick, and for that reason, you need to eliminate external dependencies.

Stub example in C#

Stubbing is a way of faking or mimicking certain behaviors in a unit test. This is a fake object that is commonly used to test the logic that you have written that interacts with a third-party API. Using stubs can be a great way to test your code’s logic without actually having to run your code against a third-party API.

Let’s see one example of stubbing. We have the following TransactionViewModel class.

public class TransactionViewModel
{
    //1
    private IWalletController _walletController;

    //2
    public TransactionViewModel(IWalletController walletController)
    {
        _walletController = walletController;
        Transactions = new List<Transaction>();
    }

    public List<Transaction> Transactions
    {
        get;
        set;
    }

    //3
    public async Task GetTransactions()
    {
        var transactions = await _walletController.GetTransactions();
        Transactions = transactions;
    }
}

The code explained:

  1. TransactionViewModel has one dependency, IWalletController
  2. TransactionViewModel gets its dependency through the constructor. In the constructor, the Transactions property is also initialized.
  3. We will test the GetTransactions method. It calls the GetTransactions method of the IWalletController and saves the result into the Transactions property.

The IWalletController interface looks like this:

public interface IWalletController
{
    Task<List<Transaction>> GetTransactions(bool forceReload = false);
    Task<List<Coin>> GetCoins(bool forceReload = false);
}

In order to write a test that checks that the Transactions property is populated with the transactions from the IWalletController, the first thing we need to do is to create an IWalletController stub. This stub object will return hard-coded data.

//1
class StubWalletController : IWalletController
{
    public Task<List<Coin>> GetCoins(bool forceReload = false)
    {
        throw new NotImplementedException();
    }

    //2
    public Task<List<Transaction>> GetTransactions(bool forceReload = false)
    {
        var task = Task.FromResult(new List<Transaction>
        {
            new Transaction { Id = 1, Amount = 2, Symbol = "BTC" },
            new Transaction { Id = 2, Amount = 3, Symbol = "ETH" },
            new Transaction { Id = 3, Amount = 7, Symbol = "ETH" },
        });
        return task;
    }
}
  1. We name the class StubWalletController. The class must implement two methods. If a method is not used in tests, it is ok to leave a default implementation, that is throw a NotImplementedException.
  2. The method that we need for our test. It returns static data.

And the test for the GetTransactions looks like this:

public class TransactionViewModelTests
{
    [Fact]
    public async void GetTransactions_populates_Transactions_property()
    {
        //Arrange
        var transactionViewModel = new TransactionViewModel(new StubWalletController());
        //Act
        await transactionViewModel.GetTransactions();
        //Assert
        Assert.Equal(3, transactionViewModel.Transactions.Count);
    }
}

In the Arrange part of the test case, we create a new instance of the TransactionViewModel. It takes an instance of the StubWalletController as the constructor parameter. In the Act part, we call the method GetTransactions. In the Assert part, we check that there are 3 transactions in the Transactions property. Those transactions came from the StubWalletController.

(Check out the post about unit testing if you want to find out more about Arrange-Act-Assert and unit testing in general.)

Mock example in C#

When we talk about mocking in unit tests, we refer to creating a dummy object that represents a real object we want to test. We can then use this dummy object to verify that the real object’s functionality works as expected.

Let see how to test the following class:

public interface IEmailSender
{
    void SendEmail(string to, string subject, string mailBody);
}

public class OrderNotifier
{
    private IEmailSender _emailSender;

    //1
    public OrderNotifier(IEmailSender emailSender)
    {
        _emailSender = emailSender;
    }

    //2
    public void NotifyThatOrderWasSent(string toAddress, string orderId)
    {
        var subject = $"Order {orderId} is on the way";
        var mailBody = $"Hello, your order {orderId} is on the way" + Environment.NewLine;
        mailBody += "It should arrive in the next 2-3 days.";

        _emailSender.SendEmail(toAddress, subject, mailBody);
    }
}
  1. The OrderNotifier class has one dependency, IEmailSender. That’s the type that sends emails in the current system.
  2. We will write a test for the NotifyThatOrderWasSent method. This method creates the subject and the body of the mail that will be sent. And the last step is to use the IEmailSender’s method SendEmail to send the email.

Before we can write tests, we need to create a fake implementation of the IEmailSender:

class MockEmailSender: IEmailSender
{
    public bool MailWasSent { get; set; }

    public void SendEmail(string to, string subject, string mailBody)
    {
        MailWasSent = true;
    }
}

This mock class has one extra property, MailWasSent. We will use this to determine whether or not the SendEmail method was called.

Ok, now the test.

public class MailSenderTests
{
    [Fact]
    public void NotifyThatOrderWasSent_sends_email()
    {
        //Arrange
        var sender = new MockEmailSender();
        var notifier = new OrderNotifier(sender);
        //Act
        notifier.NotifyThatOrderWasSent("test@gmail.com", "3223");
        //Assert
        Assert.True(sender.MailWasSent);
    }
}

In the Arrange part, we create both MockEmailSender and OrderNotifier instances. In the Act part, we call the NotifyThatOrderWasSent with the necessary parameters. Finally, in the Assert part, we use the sender variable to check that the NotifyThatOrderWasSent method sent the mail. 

What tools are out there?

As you saw in the examples above, creating a fake class manually every time can be very time-consuming. That’s why you can use a mocking framework to create a stub or mock on the fly.

Many 3rd party libraries can help with mocking and stubbing, that is, automate the process of creating mocks and stubs. The most popular are:

They are all similar in what they do. The benefit of using such libraries is to speed up the process of software testing. This is especially important in test-driven development, where you need to create tests fast.

Stub vs. mock – which one should you use?

Use a stub when:

  • Need to return fake values for your class under test
  • Class needs a dependency, but that dependency is not used in the current test

Use a mock when:

  • Want to test that the void method has called a method on a dependency
  • Want to make sure that some call to the outside world has been performed, that is, email was sent, a network API service was performed, a database call has been performed
  • Want to test that the call of the 3rd library happened from your code.

Recent Posts