Faking it
As well as asserting, one technique that is particular to test code is the use of fakes. Fakes generally fall into two categories based on what we need from them. We either want to fake data coming into the system (stub) or we want to fake a component of the system to see data on the way out (mock).There are lots of reasons that we may need to do this, such as:
- Consistency; we need to ensure that the data coming into the system is always the same. If we rely on an external source like a file or database there is always a risk on the data changing
- Performance; the sooner we get feedback from a test the better. Tests that access an external resource for data will be much slower than a test that initializes the data itself
- Simplicity; creating the input data within the test makes it visible to anyone reading the test and therefore easier to understand
- Side Effects: the whole point of doing something with software in production is to produce a side effect, something like inserting a row into a database table or sending an order to an exchange. We definitely do not want to do this each time we run a test so we will find some way to fake the side effect
- Autonomy; Having the test access an external resource creates a coupling to that resource. This is a good thing in a production system, but bad for a test. You can think of a test as a small executable that must always run in the same way. We achieve this by making each test as autonomous as possible.
Stubs
A stub is a way of faking data coming into the system.In our example of tracking changes to a PnL, lets say that each account is only allowed to trade certain instruments. These instruments would normally be stored in a database and loaded into the account when it is initialised. Wecan avoid all of that hassle using a stub.
First of all we need a way for the account to access the allowed instruments, so we will give it a class that can do this.
public class Account
{
public decimal PnL { get; private set; }
private readonly List<string> _allowedInstruments;
public Account(ProdInstrumentLoader instrumentLoader)
{
_allowedInstruments = instrumentLoader.Load(this);
}
public void Sell(int quantity, string bbg)
{
if (!_allowedInstruments.Contains(bbg))
{
throw new Exception($"Invalid inistrument: {bbg}");
}
}
}
public class ProdInstrumentLoader
{
public List<string> Load(Account acc)
{
// loads from database
}
}
This still won't work because we have coupled the class to loading the instruments from the database, so we need a way to replace ProdInstrumentLoader with a stub. We can do this by extracting an interface and coupling the Account class to the instrface instead.
public class Account
{
public decimal PnL { get; private set; }
private readonly List<string> _allowedInstruments;
public Account(InstrumentLoader instrumentLoader)
{
_allowedInstruments = instrumentLoader.Load(this);
}
public void Sell(int quantity, string bbg)
{
if (!_allowedInstruments.Contains(bbg))
{
throw new Exception($"Invalid inistrument: {bbg}");
}
}
}
public class ProdInstrumentLoader : InstrumentLoader
{
public List<string> Load(Account acc)
{
// loads from database
}
}
public interface InstrumentLoader
{
List<string> Load(Account acc);
}
Now we can create a stub version of the instrument loader that will be suitable for our test. Our stub implementation will take whatever list of instrument it is initialised with and always return them when Load is called.
public class StubInstrumentLoader : InstrumentLoader
{
private List<string> _instruments;
public StubInstrumentLoader(List<string> instruments)
{
_instruments = instruments;
}
public List<string> Load(Account acc)
{
return _instruments;
}
}
The test can now load the stubbed instrument instead of hitting the database.
[Test]
public void selling_decreases_the_pnl()
{
var instrumentLoader = new StubInstrumentLoader(new List<Instrument> { "VOD LN" });
var tradingAccount = new Account(instrumentLoader) { PnL = 0 };
tradingAccount.Sell(200, "VOD LN");
Assert.That(tradingAccount.PnL, Is.EqualTo(-200));
}
Mocks
There are lots of different ways that we can assert (see xxx for an exhaustive list), but they all fall into one of two categories.One, the most common, is checking that a value is what we expect. The other is checking that a method was called in the way that we expect and we do this with a technique called Mocking.
Continuing with our previous example, let's say that part of the requirement for creating an order is to publish its details. This will involve our account class calling a service with the order details.
public class Account
{
public decimal PnL { get; private set; }
private OrderPublisher _publisher;
public Account(OrderPublisher publisher)
{
_publisher = publisher;
}
public void Sell(int quantity, string bbg)
{
...
_publisher.NewOrder(NewOrder(quantity, bbg));
}
}
We need some way to verify that the publisher published a NewOrder with the details that we gave. We can achieve this by creating a mock version of OrderPublisher. Our mock version of the publisher doesn't actually do any publishing, instead it records whatever information is necessary for out verification.
public class MockOrderPublisher : OrderPublisher
{
public List<NewOrder> Published { get;set; } = new List<NewOrder>();
public void NewOrder(NewOrder order)
{
Published.Add(order);
}
}
We can now verify the details of the published order in a test.
[Test]
public void ensure_that_order_details_are_published()
{
var publisher = new MockOrderPublisher
var tradingAccount = new Account() { PnL = 0 };
tradingAccount.Buy(200, "AAPL");
Assert.That(publisher, Contains(1));
Assert.That(publisher.Published[0].Quantity, Is.EqualTo(200));
Assert.That(publisher.Published[0].Bbg, Is.EqualTo("AAPL"));
}
That's the basic setup for a mock. You need some way of replacing the real functionality with something that acts like it. We used a custom rolled mock here, but there are no shortage of frameworks in .Net to do this for you, Moq being one of the most popular.