Clean Up Your Spock Tests

Paweł Otorowski
TechTalks@Vattenfall
6 min readJul 6, 2020

Introduction

Writing code not covered by any tests was something really usual. Probably everyone had the opportunity to contribute to this trend. However, as time passed by, new tools have emerged, and we are increasingly focusing on the quality of the solutions we provide. Clean code is not only about creating in accordance with the best standards, well-designed classes, but also tests that cover behavior and functionalities they provide.

In this article, I would like to show you possible ways how to improve the readability and maintainability of the tests written in Spock. An example that you can find below doesn’t look very bad but still gives the opportunity to clean it up. I just wanted to make this post easier to read and think of. Believe me that I had to work with much worse tests in my career and the same approaches could have been introduced there too.

Before we start

Overview of the application that might use the StationFactory class to handle incoming InstallStationCommand through the REST API.

We are going to test a factory (widely used in the Object-Oriented Programming) that creates a station object based on the installation command.

I assume you are familiar with the basics of the Spock Framework and Groovy language. In the samples, you can find randomAlphabetic(n) method that is part of the Apache Commons Lang library.

I have created a small Java project using Apache Maven, that contains all the steps from this post and it’s available on GitHub. Each step has its own tag, so you can easily switch between them by executing git checkout {paragraph_name}.

I used to work with the Test Driven Development (TDD) approach, so there is no logic implemented in the factory, however, the necessary test is already in place - write some code there if you want to see the green light (it feels so good). For more information please check out the post written by Martin Fowler on his blog.

Starting point

Let’s start with the piece of code that tests whether our factory creates a valid object based on the installation command we pass.

Checking object created by StationFactory class based on the given command.

The name of the test describes what should we expect, but it’s not so obvious when it comes to checking out the details, because data preparation and result verification of the tested method takes most of the developer's attention. As an author, I can work with these lines, but my goal is to write code that is easy to read and maintain.

In case of any doubts, please refer to the starting-point tag that you can find here.

Data preparation

One of the first steps to create a readable test is to prepare data in the easiest possible way, to not to cloud its logic. At first sight, you can find many executions of the randomAlphabetic(n) method, that is responsible for the generation of a random string of n-length. Refactor can be started from extracting methods that are preparing test data to the separate and meaningful ones.

Preparing data for test in more meaningful way.

After this change test is still too long, but the person who reads it for the first time won’t have to think how the randomAlphabetic(n) method works and how it is important for the tested functionality (it doesn’t of course).

The time has come to shorten the number of lines that are required for preparing test data to keep this part as readable as possible.

Preparing test data simplified as much as possible.

By moving command creation to the separate method, we gain a more readable way to prepare data and also the possibility to reuse the same method again in other tests in the future. We also get yet another important information - command, that we use in our test is valid. I can imagine another case that tests our factory behavior on invalid command (for example, because some required field is missing), wherein such a situation we would expect relevant information about the problem:

Creating a station from installation command without owner.

All changes done during this step are available here.

Results verification

The next step towards readable tests is to look into the result verification part of the tested method. The value of individual fields is important but from a higher point of view, from a perspective of factory API user, the most important is that the returned station object is created properly.

Verifying created object in a more meaningful way.

Extracting verification result causes our test to contain the minimum necessary to test the factory method. Besides that, the whole class is now longer than a few minutes ago, but we are going to clean it up in the next few steps.

If any doubts, please check the code tagged here.

Extracting fixtures

After creating a readable test, it’s time to move all the methods that prepare test data to separate classes (fixtures), which can be used in the other tests we have in our project. This is also known as a third step of the TDD approach (refactoring). We should look for duplications and places that could be simplified. Organizing code into fixtures helps a lot here.

Extracting command creation allows reusing it in other tests.
Extracting address related data allows reusing it in other tests.

These methods are almost the same as they were defined in the StationFactoryTest class. The only difference is that I made them static for cleaner usage.

Using extracted methods make test class cleaner.

As you can see all the methods definitions disappeared from this test, but they are statically imported from the already created fixtures.

If there is a requirement in the future that valid country code is needed for all the test data, then change would have to be introduced in a single place only.

For the sake of simplicity, I didn’t attach all the fixtures I created in this step, but you should be able to imagine how they look like based on the above examples. Please refer to the specific tag on the GitHub repository I mentioned before in case of any doubts.

Creating support

Verification methods can also be extracted to other classes, but I will propose you here another approach just to show the difference. This also can be reused in other tests later.

Creating supports allow to keep helper methods outside of the test classes.
Using extracted support makes test class even cleaner.

Supports are most useful when you have common, heavy logic to share between tests. It hides the complexity of the necessary things that need to be done behind the scene. If the names of the methods are good enough, reading tests might be similar to reading the book.

Change done within this step is available in this tag.

Next step

This example is pretty simple, but provides great possibilities for extension, forcing us to stick to good practices. For example, if we would need to write a new test which checks whether the newly created object doesn’t come from the future, we could do it this way:

Testing installation time validation.
Creating a station installation command from the future.

As always, code added here is available in this tag.

Introducing TestDataBuilder

Preparing data with meaningful methods allows writing short and clean tests but requires creating additional method each time a new test case needs to be implemented. Introducing a builder helps to define test data without this overhead. Let’s take a look at the following example:

Introducing TestDataBuilder pattern into the existing InstallStationCommandFixture class.

Thanks to this change we ended up with a single static stationInstalled() method that provides an instance of a builder that we can use to prepare test data. Now it’s easy to change any field of the InstallStationCommand depending on the needs:

Using InstallStationCommandFixture to customize installation time in command when it’s needed.

As you can see creating a builder to provide test data might be time-consuming at the beginning but it will repay us in the future giving a flexible and readable way of preparing data in any combination is needed.

Tag for this change is available here.

Summary

Good tests are a solid base for every production working application. If they are also readable, they can be a good alternative to the documentation. It’s worth paying a little more attention to them, which will certainly be appreciated not only by colleagues from the team during code review or daily work on our code but also by new developers joining the project. Remember that developers spend more time on reading existing code than writing a new one, so high readability improves their performance.

--

--