What Is A Fake?
Unit testing can be tricky. Sometimes our class-under-test interacts with another class which should not be part of the testing. For example, we don’t want to unit test calls to actual databases and other network IO. Even though abstractions, like interfaces, protect us from calling such a collaborating class directly, yet we still have to call something. Enter the fake collaborator. So far into our journey of unit testing using fake collaborators, we have encountered Dummies, Stubs, Spies and Mocks.
Yet, there is one more type of fake collaborator: The ‘Fake'[1].
So, what is a Fake?
A Fake mimics the behaviour of a genuine collaborator class.
For example, a ‘fake’ database class may imitate the function of an actual database by providing methods to save and read data. However, data will not actually be written to a file and saved—it’ll be temporarily stored in an in-memory collection, as in this FakeCustomerDatabase
:
public class FakeCustomerDatabase : ICustomerRepository { private readonly List<Customer> Customers = new List<Customer>(); public async Task<Customer> GetCustomer(string emailAddress) { return Customers.FirstOrDefault(c => c.EmailAddress == emailAddress); } public async Task SaveCustomer(Customer customer) { Customers.Add(customer); } }
Let’s look at another example:
Say, we have the following SalesTaxCalculator
class:
public class SalesTaxCalculator { public decimal TaxRate { get; } public SalesTaxCalculator(string countryCode, ISalesTaxSelector taxSelector) { TaxRate = taxSelector.Select(countryCode); } public decimal CalcSalesTax(SalesInvoice invoice) { return invoice.Total * TaxRate; } }
The actual implementation of ISalesTaxSelector
will run off to a remote API to retrieve the tax rate for the given countryCode
. We would rather not make this API call in our unit testing of SalesTaxCalculator
. Instead, we could use a Stub or a Fake.
The unit test using a StubSalesTaxSelector
:
[Fact] public void Test_SalesTaxCalculator_CalcSalesTax_Using_StubTaxSelector() { var stubTaxSelector = new StubTaxSelector(0.20m); var taxCalculator = new SalesTaxCalculator("UK", stubTaxSelector); var salesInvoice = new SalesInvoice { Total = 123.45m }; var tax = taxCalculator.CalcSalesTax(salesInvoice); tax.Should().Be(24.69m); }
and StubTaxSelector
:
public class StubTaxSelector : ISalesTaxSelector { public decimal TaxRate { get; } public StubTaxSelector(decimal taxRate) { TaxRate = taxRate; } public decimal Select(string countryCode) { return TaxRate; } }
Or we could use a Fake
TaxSelector:
[Fact] public void Test_SalesTaxCalculator_CalcSalesTax_Using_FakeTaxSelector() { var fakeTaxSelector = new FakeTaxSelector(); var taxCalculator = new SalesTaxCalculator("UK", fakeTaxSelector); var salesInvoice = new SalesInvoice { Total = 123.45m }; var tax = taxCalculator.CalcSalesTax(salesInvoice); tax.Should().Be(24.69m); }
and
public class FakeTaxSelector : ISalesTaxSelector { public decimal Select(string countryCode) { switch (countryCode) { case "NZ": return 0.15m; case "UK": return 0.20m; default: throw new InvalidOperationException(); } } }
Here is my question: In the example, why is using the stub preferable to the fake?
Fakes are problematic. I hardly ever use them.
Do you know (or suspect) the answer? Feel free to send in your thoughts by the end of the day to olaf@codecoach.co.nz. I will shout a $5 coffee to the first person to send in the correct answer. I am happy to have that coffee with them in Auckland.
I’ll reveal the answer tomorrow.
Footnotes:
- Frequently ‘Fake’ is also used to mean fake collaborator as in dummies, stubs, spies, mocks and, of course, fakes.
Leave a Reply
Want to join the discussion?Feel free to contribute!