ARTICLE

What is a Unit Test? Part 1: an introduction

From Unit Testing: Principles, Practices, and Patterns by Vladimir Khorikov

This article covers:

  • Definition of a unit test
  • The differences between shared, private, and volatile dependencies
  • The two schools of unit testing: classical and London
  • The differences between unit, integration, and end-to-end tests

_________________________________________________________________

Take 37% off Unit Testing: Principles, Practices, and Patterns. Just enter fcckhorikov into the discount code box at checkout at manning.com.
_________________________________________________________________

Digging deeper into what a unit test is

The definition of a unit test has a surprising number of nuances, and they’re more important than one would think. Enough that the differences in interpreting those nuances led to forming two separate views on how to approach unit testing. These views are known as the classical and the London schools of unit testing. The classical school is so-called because it’s how everyone approached unit testing and test-driven development originally. The London school takes root in the Extreme Programming community in London.

This article focuses on the classical style of unit testing, but it’s important for you to know the main tenets of the London style.

I’ll give a definition of an integration test. I’ll contrast the unit and integration testing from the perspectives of the classical and London schools (yes, they have separate views on what constitutes this difference too).

Let’s start by defining a unit test.

Defining a unit test

A unit test is an automated test that:

  • Verifies a small piece of code (also known as unit).
  • Does it quickly.
  • And in an isolated manner.

The first two attributes are non-controversial. There might be some dispute as to what exactly constitutes a fast unit test because it’s a highly subjective measure, but overall, this isn’t that important. If the execution time of your test suite is good enough for you, it means that your tests are quick enough as well.

What people have vastly different opinions about is the third attribute. It’s the root of the differences between the classical and the London schools of unit testing. In this section, I describe what that isolation issue is all about and explore it from the London and then the classical school’s perspectives. As you’ll see in the next section, all other differences between the two schools flow naturally from this single disagreement on what exactly isolation means. I prefer the classical style.

The classical and the London schools of unit testing

The classical approach is also referred to as the Detroit and, sometimes, as the classicist approach to unit testing. Probably the most canonical book on the classical school is the one by Kent Beck, Test-Driven Development: By Example.

The London style is sometimes referred to as Mockist. Although the term Mockist is widely spread, people who adhere to this style of unit testing generally don’t like it; I’ll call it the London style. The most prominent proponents of this approach are Steve Freeman and Nat Pryce. I recommend their book Growing Object Oriented Software, Guided by Tests as a good source on this subject.

The root of the differences between the two schools of unit testing

What does it mean to verify a piece of code — a unit — in an isolated manner? The London school describes it as isolating the system under test itself from its collaborators. It means that if a class has a dependency on another class, or several classes, you need to substitute all such dependencies with test doubles. This allows you to focus on the class under test exclusively by separating its behavior from any external influence.

DEFINITION: A test double is an object that looks and behaves like its release-intended counterpart, but it’s a simplified version that reduces the complexity and facilitates testing. This term was introduced by Gerard Meszaros in his book xUnit Test Patterns: Refactoring Test Code (AddisonWesley, 2007). The name itself comes from the notion of a stunt double in movies.

Figure 1. Replacing the dependencies of the system under test with test doubles allows you to focus on verifying the system under test exclusively, as well as split the otherwise large interconnected object graph.

One benefit of this approach is that if the test fails, you know for sure which part of the code base is broken. This is the system under test itself. There could be no other suspects as all the class’s neighbors are replaced with the test doubles.

Another benefit is the ability to split the object graph. You see, your code base might comprise a complicated web of communicating classes. Every class in such a web has several immediate dependencies, each of which rely on dependencies of their own. Classes may even introduce circular dependencies, where the chain of dependency eventually comes back to where it started.

Trying to test such an interconnected code base is hard without test doubles. The only choice you’re left with is re-creating the full object graph in the test, which might not be a feasible task if the number of classes in it’s too high.

With test doubles, you can put a stop to this. You can substitute the immediate dependencies of a class, and by extension, you don’t have to deal with the dependencies of those dependencies, and on down the recursion path. You’re effectively breaking up the graph, and that can significantly reduce the amount of preparations you must do in a unit test.

And let’s not forget another small, but pleasant side benefit of this approach to unit test isolation. It allows you to introduce a project-wide guideline of testing only one class at a time. This establishes a simple structure in the whole unit test suite. You no longer have to think much about how to cover your code base with tests. Have a class? Create a corresponding class with unit tests! Figure 2 shows how it usually looks.

Figure 2. Isolating the class under test from its dependencies helps establish a simple test suite structure: one class with tests for each class in the production code.

The isolation issue: the London take

Let’s now look at some examples. I’ll first explain the London take on the isolation issue. I’ll show sample tests written in the classical style and then rewrite them using the London approach.

Let’s say that we operate an online store. We’ve only one simple use case in our sample application: a customer can purchase a product. When there’s enough inventory in the store, the purchase is deemed to be successful and the amount of the product in the store is reduced by the purchase’s amount. If there’s not enough product, the purchase isn’t successful and nothing happens in the store.

Listing 1 shows two tests verifying that a purchase succeeds only when there’s enough inventory in the store. The tests are written in the classical style and use the typical three-phase sequence: arrange, act, and assert (AAA for short).

Listing 1 Tests written using the classical style of unit testing.

[Fact]
public void Purchase_succeeds_when_enough_inventory()
{
// Arrange
var store = new Store();
store.AddInventory(Product.Shampoo, 10);
var customer = new Customer();

// Act
bool success = customer.Purchase(store, Product.Shampoo, 5);

// Assert
Assert.True(success);
Assert.Equal(5, store.GetInventory(Product.Shampoo));
}

[Fact]
public void Purchase_fails_when_not_enough_inventory()
{
// Arrange
var store = new Store();
store.AddInventory(Product.Shampoo, 10);
var customer = new Customer();

// Act
bool success = customer.Purchase(store, Product.Shampoo, 15);

// Assert
Assert.False(success);
Assert.Equal(10, store.GetInventory(Product.Shampoo));
}

public enum Product
{
Shampoo,
Book
}

❶ the product amount in the store’s reduced by five and the product

❷ amount in the store remains unchanged

As you can see from the listing, the arranged part is where the tests make ready all dependencies and the system under test itself. The call to customer.Purchase is the act phase, where you exercise the behavior you want to verify. The assert statements are the verification stage, where you check to see if the behavior led to the expected results.

During the arrange phase, there are two kinds of objects that the tests put together: the system under test (SUT) and one collaborator. Customer is the SUT. The collaborator is Store. We need the collaborator for two reasons.

One is to get the method under test to work because customer.Purchase requires a Store instance as an argument.

Second, we need it in the assertion phase because one of the results of customer.Purchase is a potential decrease of the product amount in the store. Product.Shampoo and numbers 5 and 15 are constants.

This is an example of the classical style of unit testing: the test doesn’t substitute the collaborator (the Store class) but rather uses a production-ready instance of it.

One of the natural outcomes of this style is that the test now effectively verifies both Customer and Store, not just Customer. Any bug in the inner workings of Store that affects Customer causes these unit tests to fail, even if Customer still works correctly. The two classes aren’t isolated from each other in the tests.

Let’s now modify the example towards the London style. I’ll take the same tests and substitute the Store instances with test doubles, specifically with mocks. I’ll be using Moq (https://github.com/moq/moq4) as the mocking framework.

DEFINITION: A mock is a special kind of test double which allows you to examine interactions between the system under test and its collaborators.

The main thing to remember is that mocks are a subset of test doubles. People often use the terms test double and mock synonymously, but technically they aren’t identical. Test double is an overarching term that describes all kinds of non-production-ready, fake dependencies in a test, whereas mock is only one kind of such dependencies.

Again, I’m using Moq as the mocking framework but there are quite a few of equally good alternatives, such as for example NSubstitute (https://github.com/ nsubstitute/NSubstitute). Analogous frameworks can be found in all object oriented languages. For instance, in the Java world, you can use Mockito, JMock, or EasyMock.

Listing 2 shows how the tests look after isolating Customer from its collaborator, Store.

Listing 2 Tests written using the London style of unit testing.

[Fact]
public void Purchase_succeeds_when_enough_inventory()
{
// Arrange
var storeMock = new Mock<IStore>();
storeMock
.Setup(x => x.HasEnoughInventory(Product.Shampoo, 5))
.Returns(true);
var customer = new Customer();

// Act
bool success = customer.Purchase(
storeMock.Object, Product.Shampoo, 5);

// Assert
Assert.True(success);
storeMock.Verify(
x => x.RemoveInventory(Product.Shampoo, 5),
Times.Once);
}

[Fact]
public void Purchase_fails_when_not_enough_inventory()
{
// Arrange
var storeMock = new Mock<IStore>();
storeMock
.Setup(x => x.HasEnoughInventory(Product.Shampoo, 5))
.Returns(false);
var customer = new Customer();

// Act
bool success = customer.Purchase(
storeMock.Object, Product.Shampoo, 5);

// Assert
Assert.False(success);
storeMock.Verify(
x => x.RemoveInventory(Product.Shampoo, 5),
Times.Never);
}

Note how different these tests are. In the London-style arrange phase, the tests no longer instantiate a production-ready instance of Store, but instead create a substitution for it using Moq’s built-in class Mock<T>.

Furthermore, instead of modifying the state of Store by adding a shampoo inventory to it, we’re directly telling the mock how to respond to calls to HasEnoughInventory. The mock is reacting to this request the way the tests need to, regardless of the actual state of Store. In fact, there’s no state in Store anymore as we introduced an IStore interface and mocking that interface instead of the Store class.

The work with interfaces is a topic for another article. For now, make a note that interfaces are required for isolating the system under test from its collaborators.

The assertion phase has changed too, and this is where the key difference between the classical and the London styles lies. In the London style, we still check the output from customer.Purchase as before, but the way we verify that the customer did the right thing to the store is different. With the classical approach, we did this by asserting against the store’s state. Now, with the London style, we examine the interactions between Customer and Store: the tests now check to see if the customer made the correct call on the store. We do this by passing the method the customer should call on the store (x.RemoveInventory), and also the number of times it should do this. If the purchases succeed, the customer should call this method once (Times.Once). If the purchases fail, the customer shouldn’t call it at all (Times.Never).

The isolation issue: the classical take

To reiterate, the London style approaches the isolation requirement by segregating the piece of code under test from its collaborators with the help of test doubles; specifically, mocks.

Interestingly enough, this point of view also affects your standpoint on what constitutes a small piece of code, a unit. Here are all the attributes of a unit test once again:

  1. A unit test verifies a small piece of code (a unit).

2. Does it quickly.

3. And in an isolated manner.

Some room can be found in the possible interpretations of the first attribute as well. How small should a small piece of code be? As you saw from the previous section, if you adopt the position of isolating every individual class from each other, then it’s natural to accept that the piece of code under test should also be a single class, or a method inside that class. It can’t be more than that due to the way you approach the isolation issue.

In some cases, you might test a couple of classes at once, but in general, you’ll always strive for maintaining this guideline of unit testing one class at a time.

As I mentioned previously, there’s another way to interpret the isolation attribute — the classical way. It’s not the code that needs to be tested in an isolated manner, but unit tests should be run in isolation from each other, allowing you to run the tests in parallel, sequentially, and in any order, whatever fits you best, and they still won’t affect each other’s outcome.

Isolating tests from each other means that it’s fine to exercise several classes at once as long as they all reside in the memory and don’t reach out to a shared state, through which the tests can communicate and affect each other’s execution context. Typical examples of such a shared state are out-of-process dependencies — the database, the file system, and so on.

For instance, one test could create a customer in the database as part of its arrange phase and another test deletes it as part of its own arrange phase, before the first test completes executing. If you run these two tests in parallel, the first test would fail; not because the production code’s broken but rather because of the interference from the second test.

This take on the isolation issue entails a much more modest view on the use of mocks and other test doubles. You can still use them but you normally do that only for those dependencies that introduce a shared state between tests. Figure 3 shows how it looks.

Figure 3. Isolating unit tests from each other entails isolating the class under test from shared dependencies only. Private dependencies can be kept intact.

Note that shared dependencies are shared between unit tests, not between classes under test (units). In that sense, a singleton dependency isn’t shared as long as you’re able to create a new instance of it in each test. Although there’s only one instance of a singleton in the production code, tests may not follow this pattern, nor reuse that singleton. Such a dependency is private.

For example, there’s normally only one instance of a configuration class which is re-used across all production code. If it’s injected into the SUT the way all other dependencies are, say, via constructor, you can create a new instance of it in each test, and you don’t have to maintain a single instance throughout the test suite. You can’t create a new file system or a database, but they must be either shared between tests or substituted away with test doubles.

Shared vs volatile dependencies

Another term has a similar meaning: volatile dependency. I recommend Dependency Injection: Principles, Practices, Patterns by Steven van Deursen and Mark Seeman (Manning Publications, 2018) as a go-to book on the topic of dependency management.

A volatile dependency is a dependency that exhibits one of the following properties:

• It introduces a requirement to set up and configure a runtime environment in addition to what is installed on a developer’s machine by default: a database or an API service are good examples here. They require additional setup and aren’t installed on machines in your organization by default.

• It contains non-deterministic behavior: examples are a random number generator or a class returning the current date and time. These dependencies are non-deterministic because they provide different results on each invocation.

As you can see, there’s an overlap between the notions of shared and volatile dependencies. For example, a dependency on the database is both shared and volatile, but it isn’t the case for the file system. The file system isn’t volatile because it’s installed on every developer’s machine and it behaves deterministically in the vast majority of cases. Still, the file system introduces a means by which the unit tests can interfere with each other’s execution context, hence it’s shared. Likewise, a random number generator’s volatile but because you can supply a separate instance of it to each test, it isn’t shared.

Another reason for substituting shared dependencies is increasing the test execution speed. Shared dependencies almost always reside outside the execution process, but private dependencies usually don’t cross that boundary. Because of that, calls to shared dependencies, such as the database or the file system, take more time than to private dependencies. Because the necessity to run quickly is the second attribute of the unit test definition, such calls push the tests with shared dependencies out of the realm of unit testing into the area of integration testing. I’ll talk more about integration testing later in this article.

This alternative view on the isolation also leads to a different take on what constitutes a small piece of code, a unit. It doesn’t necessarily have to be limited to a class. You can unit test a group of classes, as long as neither of them is a shared dependency.

The classical and London schools of unit testing

As you can see, the root of the differences between the London and the classical schools is the isolation attribute. The London school views it as isolation of the system under test from its collaborators, whereas the classical school views it as isolation of unit tests themselves from each other.

This seemingly minor difference has led to a vast disagreement in how to approach unit testing, which, as you already know, produced the two schools of thought. Overall, the disagreement between the schools’ spans across three major topics:

  • The isolation requirement.
  • The view on what constitutes a piece of code under test (a unit).
  • Handling dependencies.

Table 1 sums it all up.

Table 1. The differences between the London and the classical schools of unit testing summed up by the approach to isolation, the size of a unit, and the use of test doubles.

Summary

A unit test has three attributes:

  • A unit test verifies a small piece of code.
  • Does it quickly.
  • And in an isolated manner.

The isolation issue is the one which is disputed the most. It even led to forming two separate schools of unit testing: classical (Detroit) and London (mockist). This difference, in turn, affects the view on what exactly constitutes a small piece of code, a unit, and the treatment of the SUT’s dependencies.

The London school states that each unit under test should be isolated from each other. A unit under test is a unit of code, usually a class. All its dependencies, except immutable dependencies, should be replaced with test doubles in tests.

That’s all for now, stay tuned for part 2.

If you want to learn more about the book, check it out on liveBook here and see this slide deck.

Check out more great free content from our titles on our Free Content Center.

Written by

Follow Manning Publications on Medium for free content and exclusive discounts.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store