I originally posted an extended version of this post on my blog a couple of weeks ago. It's part of a series I've been publishing, called "Unit Testing 101"
Do you know what are fakes? Are stubs and mocks the same thing? Let's see what are the differences between them.
Fakes
The best analogy to understand fakes are flight simulators. With a flight simulator, teachers can recreate flight and environment conditions to train and test their pilot students in controlled scenarios.
Fakes are like flight simulators. Fakes are classes or components that replace external dependencies in your tests. With fakes, you can return values, throw exceptions or record method calls to test the code around it. Fakes create the conditions to test our code in controlled scenarios.
Mocks vs Stubs
Now that we know what fakes are, let's see two types of fakes: mocks and stubs.
Fakes represent a broad category of test "simulators". Mocks and stubs are two types of fakes.
Stubs provide values or exceptions to the code under test and mocks are used to assert that a method was called with the right parameters.
OrderService example
To better understand the difference between mocks and stubs, let's use an example. Let's process online orders with an OrderService
class.
This OrderService
checks if an item has stock available to then charge a credit card. Imagine it uses an online payment processing software to take payments and an API request to a microservice to find the stock of an item. We use two interfaces, IPaymentGateway
and IStockService
, to represent the two dependencies. Something like this,
public class OrderService
{
private readonly IPaymentGateway _paymentGateway;
private readonly IStockService _stockService;
public OrderService(IPaymentGateway paymentGateway, IStockService stockService)
{
_paymentGateway = paymentGateway;
_stockService = stockService;
}
public OrderResult PlaceOrder(Order order)
{
if (!_stockService.IsStockAvailable(order))
{
throw new OutOfStockException();
}
_paymentGateway.ProcessPayment(order);
return new PlaceOrderResult(order);
}
}
To test the OrderService
class, we should check two things. It should throw an exception if the purchased item doesn't have stock. And, it should take a payment if the purchased item has enough stock.
Let's write a test for the scenario of an item in stock.
Fake for inventory
First, we need a fake that returns stock available for any order. Let's call it: AlwaysAvailableStockService
. It looks like this:
public class AlwaysAvailableStockService : IStockService
{
public bool IsStockAvailable(Order order)
{
return true;
}
}
As its name implies, it will always return stock no matter the order we pass.
Fake for payment gateway
Second, the OrderService
works if it charges a credit card. But, we don't want to charge a real credit card every time we run our test. That would be expensive!
Let's use a fake to record if the payment gateway was call or not. Let's call this fake: FakePaymentGateway
. It looks like this:
public class FakePaymentGateway : IPaymentGateway
{
public bool WasCalled;
public void ProcessPayment(Order order)
{
WasCalled = true;
}
}
It has a public field WasCalled
we set to true
when the method ProcessPayment()
is called.
Now that we have the AlwaysAvailableStockService
and FakePaymentGateway
in place, let's write the actual test.
[TestClass]
public class OrderServiceTests
{
[TestMethod]
public void PlaceOrder_ItemInStock_CallsPaymentGateway()
{
var paymentGateway = new FakePaymentGateway();
var stockService = new AlwaysAvailableStockService();
var service = new OrderService(paymentGateway, stockService);
var order = new Order();
service.PlaceOrder(order);
Assert.IsTrue(paymentGateway.WasCalled);
}
}
The AlwaysAvailableStockService
fake is there to provide an indirect input value for our test. It's an stub. And, the FakePaymentGateway
is used to assert that the OrderService
made a call to charge a credit card. It's a mock. Actually, we could call it MockPaymentGateway
.
Voilà! Those are the difference between stubs and mocks. Again, fakes are like test "simulators". Stubs provides values and mocks are used to assert. Next time, you work with external dependencies in your tests, ask if you need a stub or a mock. They're not the same thing.
Upgrade your unit testing skills with my course: Mastering C# Unit Testing with Real-world Examples on Udemy. Practice with hands-on exercises and learn best practices by refactoring real-world unit tests.
Happy testing!