Tangles in Test Code: Long Arrange/Given/Setup

Michael Kutz
4 min readMar 8, 2024
Some test code with long arrange steps and some symbols in the foreground.

There is a certain sloppiness in the industry when writing test code. This causes what I call tangles. One of the most frequent ones is the usage of long arrange (or given or setup) steps.

Why is this a Tangle?

According to this definition of the Arrange/Act/Assert pattern, this first part of a test should

Perform the setup and initialization required for the test.

Which is a fair, true and quite common understanding.

I personally like the definition from Martin Fowler’s Given When Then better. According to it

The given part describes the state of the world before you begin the behavior you’re specifying in this scenario. You can think of it as the pre-conditions to the test.

Why? Because I believe that one of the most important qualities of a test an immediately obvious intention.

The me arrange code is not merely about doing the technically necessary, but about describing as clear and concise as possible how the test case at hand differs from others.

  • Are the inputs different? How?
  • Is the unit under test configured in some special way?
  • Is a dependency of the test subject changed?

In short what’s the state under test. And please don’t make me read a wall of text of things every test needs. If needed put it somewhere I don’t need to read it, so the essential part is obvious.

Consider the following example:

@Test
void age_almost_birthday() {
// ARRANGE
var gilly =
new Unicorn(
randomUUID(),
"Gilly",
ManeColor.RED,
111,
11,
LocalDate.now().minusYears(62).plusDays(1));

// ACT/ASSERT
assertThat(gilly.age()).isEqualTo(61);
}

Not too much code. Actually only one statement. Eight lines is not long, right? But what a terrible ration of noise to signal: 1/8! The one and only important line in that arrange step is the used birthday in the very last line. Not too bad you might think, but this test may be surrounded by others testing very different aspects which require the other parameters to change. The difference between these tests would be obscured and not immediately obvious as it should be.

How to Untangle?

Arrange Helper Method

In some cases, it would make sense to create a method. Either in the test class itself or in a utility class if it is required across different tests.

This is useful in two ways:

  • (obviously) it reduces the lines of code in the arrange steps, and
  • the method name can make the intention behind it more obvious.

In my experience this is particularly useful for arrange steps that have multiple statements to achieve a certain state. E.g. instruct mocks and stubs, or change configuration parameters.

In the above example we could create a method

Unicorn createUnicornBornAt(LocalDate birthday)
return new Unicorn(
randomUUID(),
"Gilly",
ManeColor.RED,
111,
11,
birthday);
}

Which reduces the above test to

@Test
void age_almost_birthday() {
// ARRANGE
var gilly = createUnicornBornAt(
LocalDate.now().minusYears(62).plusDays(1));

// ACT/ASSERT
assertThat(gilly.age()).isEqualTo(61);
}

Test Data Builder

Another way to reduce the noise in arrange blocks is using a Builder specialized for tests in a way that it uses a lot of defaults and/or random data generators.

For the above example we could use the following builder

public class UnicornTestDataBuilder {

private final SecureRandom random = new SecureRandom();
private UUID id = randomUUID();
private String name = randomAlphabetic(8);
private ManeColor maneColor = ManeColor.values()[random.nextInt(ManeColor.values().length)];
private Integer hornLength = random.nextInt(1, 101);
private Integer hornDiameter = random.nextInt(1, 41);
private LocalDate dateOfBirth =
LocalDate.now()
.minusDays(random.nextInt(0, 31))
.minusMonths(random.nextInt(0, 13))
.minusYears(random.nextInt(0, 101));

private UnicornTestDataBuilder() {}
public static UnicornTestDataBuilder aUnicorn() {
return new UnicornTestDataBuilder();
}

public UnicornTestDataBuilder dateOfBirth(LocalDate dateOfBirth) {
this.dateOfBirth = dateOfBirth;
return this;
}

// … more manipulators

public Unicorn build() {
return new Unicorn(id, name, maneColor, hornLength, hornDiameter, dateOfBirth);
}
}

This allows us to reduce the above test to

@Test
void age_almost_birthday() {
// ARRANGE
var gilly = aUnicorn()
.dateOfBirth(LocalDate.now().minusYears(62).plusDays(1))
.build();

// ACT/ASSERT
assertThat(gilly.age()).isEqualTo(61);
}

This might be one more line compared to the above Arrange Helper Method, but Test Data Builders are much more versatile and can reduce code duplication quite a lot. I would much prefer it over creating multiple Arrange Helper Methods for every property or even combinations of properties.

Conclusion

While Long Arrange is one of the most common tangle I know, it is also fairly easy to fix with very light weight recipes. Of course applying these becomes more work, the bigger the tangled test suite is.

One can apply following the Campsite rule (always leave a place better than you found it). However, notice that a quite common way to extend your tests is to

  1. find the test class,
  2. copy a test case that’s similar to the one you want to create, and
  3. change it according to your needs.

Hence refactoring your test suite with the Campsite rule requires some serious discipline. Applying it everywhere in one go is less effortful in my experience.

The code blocks in this article are partly simplified versions of example in the Untangle Your Spaghetti Test Code workshop, I created with Christian Baumann. You can find the full code and a lot more tangles on GitHub.

--

--

Michael Kutz

I've been a software engineer since 2009, worked in various agile projects & got a taste for quality assurance. Today I'm a quality engineer at REWE digital.