London and Detroit schools of unit tests
London and Detroit schools are two popular ways of writing unit tests for our applications. They differ in several details, notably the answer to the question “what is a unit”. It’s likely that you already know them, just not by their names. We may think that they compete with each other, however each has its own niche. Let’s compare those two styles and see, where they score.
A bit of history for curious
The city-related names of the two schools are tied to the origins of TDD. In 1996, Kent Beck was hired to rescue the project of an internal payroll system in Chrysler Corporation. The team adopted a way of working that was later known as Extreme Programming. The book Test-Driven Development By Example codified the practice. The headquarters of Chrysler is in Auburn Hills, a suburb of Detroit, hence the name: Detroit School.
The origins of London School can be traced to Extreme Tuesday Club. It formed in London in early 2000-s and attracted the top talents of British XP scene. Eventually, Steve Freeman and Nat Pryce developed their own vision of TDD, later codified in the book Growing Object-Oriented Software Guided by Tests.
What is a unit? That is the question…
The main difference between the two schools is the size of a unit. London school is very restrictive here. The unit is nearly always equal to a single class. In Detroit school, the size of a unit is much more flexible. Generally, a unit is a collection of related classes, such as single component or module, e.g. that provides a single functionality. The actual number of classes can be as small as 1-2 classes, and as big as tens or even hundreds (!) of classes. It must just make sense.
In both cases, we replace external dependencies of a unit with test doubles. London school takes it to the extreme. Since units are individual classes, all other classes must be mocked, maybe except immutable data classes. In Detroit school, units also have dependencies that we need to replace. However, the size and complexity of a unit may be too high to use mocking libraries. Instead, we write standalone test implementations of the dependencies. For example, our unit may use database. We would replace it with an in-memory implementation that provides a subset of the functionality of the original database.
London school of unit tests
Consequences of using the London school:
- fine-grained testing of every class interaction,
- extensive use of mocking libraries,
- mocked behavior is usually very simple, because we only need the minimum to make the given test pass,
- risk of changing the behavior of the class, but forgetting to update the mocks in the tests of other classes,
- more interfaces in the production code to simplify mocking,
- tests tied to the code architecture: any rework/refactoring requires rewriting tests.
Note: due to the large use of mocks, this style is sometimes called “the mockist” style.
Detroit school of unit tests
Consequences of using the Detroit school:
- a single test can verify even a complex behavior and interactions between classes,
- in-memory test doubles to replace dependencies can be very complex,
- due to the above, mocking libraries are rarely used,
- properly written unit tests are immune to the internal rework and refactoring within a unit.
Note: Because this style was historically the first one, it is sometimes called “the classicist” style.
It’s important to note that every unit test environment has maintenance cost. The difference between the two school lies in where this cost is. In London school, the cost is related to the coupling between classes. If your classes are tightly coupled, doing even a simple refactor (e.g. splitting the class into two) may trigger rewriting tens or hundreds of unit tests. If you change the behavior of class A, you must find all mocks of this class and update them, too. For this reason, this school promotes projects with loosely coupled, rather independent classes. If your project looks like this, you will keep the maintenance cost under control.
In Detroit school, the complexity lies in the test doubles. Here, you can’t just use a dumb mock like “expect 2 passed as an argument and return 7”. Your test doubles will usually be stateful and reimplement a part of the original functionality. It requires both skills and time to do it properly. The development and maintenance cost must have justification. If you often need to change the code structure, and just need to ensure that the logic still works as expected, it can pay off.
Which school of unit tests should I choose?
Remember, we’re not fanatics. We’re engineers, therefore the answer is simple: it depends. We have just learned, what each shool gives and how we pay for it in terms of maintenance cost. It simply does not just work in this way that I’m in camp A, you are in camp B and we are enemies. No, that’s not a war. It’s about balancing the costs and picking up the right solution for your project. Below, you can see how the type of the project can influence your choice.
Case for London school of unit tests
London school of unit tests is great for libraries. In a library, classes tend to be loosely coupled. A library is usually not a complete solution (contrary to a framework), but just a set of public classes plus some internals. It is the user who picks up some of them and builds an actual project. Libraries must provide some guarantees about the behavior of their public API: “method X sorts the data internall in a stable way, method A calls the method B from your implementation of the interface C in a particular order”. Because of the compatibility, the interactions between public classes should not change too often. We must protect both the binary and behavioral compatibility.
Because of this immutability and loose coupling, the maintenance cost of London school unit tests is low in libraries, even for bigger projects. Mocks are easy to use and cheap, especially if the behavior does not change too often. They are also great for codifying the behavior contract of the API.
Case for Detroit school of unit tests
Detroit school of unit tests is great for applications and microservices. Here, we don’t care that much about the internal class structure. The only thing that matters are the business objectives of the project. If we add or change an objective, the project should quickly respond to the change. Even a small new requirement may trigger a large rework of the code structure. To verify that the change did not break the functionality, the code of the tests should be stable and should not change too often. It’s worth paying the cost of writing complex test doubles just for the sake of confidence in our unit tests and quick rework/refactoring of the production code.
In this article, we discussed the theoretical objectives of two schools of writing unit tests. We have also discovered that they do not compete with each other. Both have pros and cons, and therefore target different use cases. Over the course of the next weeks, we will make a deeper dive into both styles and see, how to use them wisely:
- London school of unit tests: mock wisely (planned)
- Detroit school of unit tests: prepare your code (planned)
- How to test race conditions in unit tests (planned)
I can also recommend one of my past articles Building a Kotlin DSL for your automatic tests. It shows, how to build a nice DSL for unit tests in a Detroit style with Kotlin.