Void’s Vault

Knowledge source for efficiency.

Use the Builder Pattern for Clean Tests

The vast majority of the unit tests we write mandates an initial context. This post is all about cleaning this initial part so the tests read like well written proses that go straight to the points. I give all the credits of this post to Elapse Technologies as this is a quick reference to one of their many blog entries.

First of all, there are basically three main parts in a test. In the BDD terminology, these parts are:

  • Given
  • When
  • Then

In the Given part, we setup the initial context of a test. It’s also called the Arrange phase. In the When part, we do the test. It’s also called the Act. Finally, the Then part is the one where we verify that the unit we are testing will do the job required. It’s also called the Assert phase. Oh, by the way, notice that I wrote in the future tense, since I assume that you are a TDD adept… right?

The thing is that the last two parts each contain only one line most of the time since a test do one thing, one thing well and one thing only. Unfortunately, it’s often NOT the case for the Given phase, which tends to represent more than 60% of all the test code.

There are of course multiple solutions to this ugliness. Among them is the possibility to use initialization methods (@Before in Java, [TestInitialize()] in C#). Another one is to extract groups of initialization lines to private methods. While these techniques can help a lot, they may not be very appropriate in all cases.

I send my thanks to Elapse Technologies for posting this excellent blog article (in french) about an efficient alternative for cleaning the test setup. This technique consist of using the Builder design pattern.

Example without the Builder:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void givenACarWith2DoorsAlreadyUnlockedWhenUnlockThenAllDoorsUnlocked() {
// Given (context)
Car aCarWith2DoorsUnloked = new Car(Color.RED)

Door driverDoor = new Door(Size.LARGE, Orientation.FRONT_DRIVER);
driverDoor.unlock();
aCarWith2Doors.addDoor(driverDoor);

Door passengerDoor = new Door(Size.LARGE, Orientation.FRONT_PASSENGER);
passengerDoor.unlock();
aCarWith2Doors.addDoor(passengerDoor);

// When
aCarWith2DoorsUnloked.unlock()

// Then
assertFalse("All doors should be unlocked", aCarWith2DoorsUnloked.allDoorLocked());
}

In the example above, there’s a high risk of having to setup the car in a different configuration for the other tests. Creating all the possible configurations is not a good idea, since it would pollute the test code by causing needless complexity. Therefore, the initialization methods or the private methods would be of no help.

Another problem is that the initialization phase is far away from the action we want to test. Indeed, creating a car and unlocking a car’s door are two different concerns.

Here’s what we could do with a Builder:

1
2
3
4
5
6
7
8
9
10
11
@Test
public void givenACarWith2DoorsAlreadyUnlockedWhenUnlockThenAllDoorsUnlocked() {
Car aCarWith2DoorsUnlocked = aCar().withDoors(
aDoor().unlocked().build(),
aDoor().unlocked().build()
).build();

aCarWith2DoorsUnloked.unlock()

assertFalse("All doors should be unlocked", aCarWith2DoorsUnloked.allDoorLocked());
}

Wow! How impressive! Look how many lines disappeared! You don’t have to care anymore about the creation, and the code can read much better, just like a story. Also, note that because the code has shrunk, the comments separating the three test phases were removed, since we don’t need them anymore. Only vertical spacing can tell what code line is for which test phase.

While discussing with Felix-Antoine Bourbonnais, one of my collegue and friend from Elapse Technologies, he told me that one of his students at Laval University (we are both teaching courses there) suggested a beautiful improvement to the Builder technique. This technique consist in hiding the build statements while offering a BDD-like readability.

1
2
3
4
5
6
7
8
9
10
@Test
public void givenACarWith2DoorsAlreadyUnlockedWhenUnlockThenAllDoorsUnlocked() {
Car aCarWith2DoorsUnlocked = given(aCar()
                             .with(aDoor().unlocked())
                             .and().with(aDoor().unlocked()));

aCarWith2DoorsUnloked.unlock()

assertFalse("All doors should be unlocked", aCarWith2DoorsUnloked.allDoorLocked());
}

As you probably guessed it, the given() method takes the CarBuilder as argument and just call the .build(). The same trick is applied to the with() method to build the DoorBuilder. Oh, one last trick is the .and() method, which is an empty method that only returns the builder. This hides the polluting details while making it readable just like a prose. Let me read that for you:

Given a car with a door unlocked and with a door unlocked, When I unlock the car, Then all doors should be unlocked.

Mind blown.

My jaw dropped when I realized how cool that was, so I needed to spread the word. Enjoy!