The Hows of Testing - Unit Tests

So let’s get into the nuts and bolts of our test setup. This is an overview of how we currently write integration and unit tests on our project. It’s by no means the “authoritative” guide to testing, but by listing a few things that have worked for us I hope to inspire other teams to look into testing on their projects as well. One of the things I hear most from other teams is “we’d like to get into unit testing, but have no idea where to start” – hopefully this serves as a guide.

Define: Tests

There’s two main terms I use when talking about tests. Unit testing is writing a test that focuses on a specific method, and that method only. The goal of a unit test is to ensure the logic within that one method is sound and able to handle any allowed type of input. The main idea is that you abstract away all the external dependencies to that method, like a UI or database or file system, and focus just on the lines of code in the method you’re testing. This is usually done using a mocking framework (more info on that below). I usually focus on testing the “business logic” layer of an application with unit tests, although it can be applied to almost any other layer given a proper setup.

Integration tests are tests that span more than one class. The idea of an integration test is to test the interaction between different layers of your application and external resources. These can range from “complete” integration tests that go all the way from clicking a button on your UI to the data layer and back, to tests that only involve interaction between two classes. Most integration tests (but not all!) involve at least one external dependency on your project, such as a web service, a UI, a database, a file system, or other external resources. Integration tests can use mocking techniques as well.

Unit Tests

Now that we have definitions out of the way, I can give an overview of how we implement our tests. With unit tests, we use a mocking framework to abstract away dependencies on other classes and resources. A mocking framework is something that you can use to imitate the behaviour of another class. Most mocking frameworks are designed to replace interface definitions, and give you a way to assert that certain methods or properties have been called in your test. We use a framework called FakeItEasy for our unit and integration tests, but there are lots of great frameworks available that provide similar functionality.

To prep your code to be “mocked out”, you have to provide a way to easily substitute your concrete implementations with mock classes. This can be done with dependency injection, but we take a simpler approach on our team and use properties.

1
2
3
4
5
6
7
8
9
10
private IInvoiceService _invoiceService;
public IInvoiceService InvoiceService
{
  //if we haven't manually set an invoice service, 
  //new up a concrete implementation
  get { return _invoiceService ?? 
      (_invoiceService = new InvoiceService()); }
  //if someone gives us an invoice service instance to use, use it
  set { _invoiceService = value; }
}

With this simple pattern, we can easily substitute our mocked services for the concrete ones, and in our production code we don’t have to do anything extra to tell our classes that it needs to use a concrete implementation. This can also be done with an overloaded constructor, although I prefer this method personally as it forces you to set each individual service explicitly instead of just handing a list of arguments over to the object.

Once our class is ready to be tested, it’s a simple matter of a “Given When Then” pattern for our test. In example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[TestMethod]
public void GetAnEmptyListOfInvoices()
{
  //given: the invoice service has no invoices
  var aggregator = new InvoiceAggregator();
  //fake it easy syntax for making a fake object based on an interface
  var mockInvoiceService = A.Fake<IInvoiceService>();
  aggregator.InvoiceService = mockInvoiceService;
  //fakeiteasy syntax for mocking out a call to "GetAllInvoices"
  //return an empty array when called
  A.CallTo(() => mockInvoiceService.GetAllInvoices())
      .Returns(new Invoice[] {});
  
  //when: we call "get total invoices" on aggregator
  //this executes our actual business logic code with the mocked service
  //as far as our code is concerned, it's business as usual
  var result = aggregator.GetTotalInvoices();
  
  //then: the result should be null
  result.Should().BeNull();
}

The Given When Then pattern is a good blueprint for any test: given a state of the system (we have no invoices), when I do something (call getTotalInvoices), then I expect these results (result should be null).

You’ll also notice how we do our asserts – using the FluentAssertions library. The reason we picked both FakeItEasy and FluentAssertions was for readability. One of the benefits of having a library of unit and integration tests is that you have a live and always up to date collection of executable documentation, and having your documentation readable is always a bonus. FakeItEasy expectations read like a sentence: “A call to invoice service’s get all invoices returns an empty array of invoices”. As do FluentAssertions: “The result should be null”.

Hopefully this is enough to get started on unit tests. I do highly recommend the libraries we use, but there are a lot of other suitable frameworks for writing your unit tests, such as NMock, NUnit, Machine.Specifications, and others. I recommend if you’re going to start unit testing your projects, do a research spike and play around with some frameworks until you find the one that works with your team.

Comments