Table of Contents
In this post, I’ll show you how to write more readable Unit Tests using a Behavior-Driven Development style (BDD). This is a coding style that is very easy to adopt and, at the same time, brings a huge benefit: it increases your test readability (a lot). Besides, it’s a small change that may drive you to go full-BDD (i.e. writing your test scenarios before your code).
With a practical example, you’ll see how to make this change with two very popular libraries: Mockito (and its BDDMockito implementation) and AssertJ.
The code in Unit Tests tend to be disorganized: some mocks here and there, interleaved with assertions and test method calls. It’s not a huge problem but, if you have many unit tests (as maybe you should), maintaining them is tricky. As a developer, I use Unit Tests to understand the functionality that someone else has written before. However, if the test is a mess, that task is much more complicated (or near impossible in some cases).
There is even a worse scenario in which, apart from the tests, you also count with documentation for that specific functionality but you have no idea how the code maps to those requirements. The tests may be working perfectly and covering that functionality as it’s needed but, not being able to derive the application flow from their code is a clear symptom of bad readability of your Unit Tests, that makes them harder (and more expensive) to maintain.
BDD brings to the table not only a human-friendly style of writing test scenarios but also the important idea that you should be doing that before writing the code that implements your functionality (thereby the name Behavior-Driven Development). In this article, I focus on the first part: the writing style. As I introduced before, it’s the easiest change and improves a lot your code. Also, switching to that coding style can enable the bigger move: all those given-when-then scenario descriptions may activate people’s willing to try to write the tests before the code. And that would come with some other advantages like a more efficient way to extract the application’s requirements, which avoids wasting time in implementing the wrong features.
To recap, the biggest two advantages are:
- Much better test readability, which leads to less time required to maintain your unit tests.
- Your test code is your documentation and will be maintained implicitly when the requirements change (otherwise your build will fail). There is no need for extra documentation on separate docs that get obsolete very quickly.
As usual, I created a practical example to show you how to create BDD Unit Tests with Mockito and AssertJ. The functionality is very simple. To make the experiment yourself of how the code documents your application, you can navigate to the complete test class written in BDD style and try to derive it from there: you’ll get the idea instantly. For educational purposes, I’ll give you a text-description of what this implementation should do anyway.
This first version of the app Population (in a very early stage) should allow inserting Cities and retrieving them. A city is no more than a name and a number that represents its population, and it has a unique identifier in the system. We need to write some business logic to cope with the following requirements:
- A city will be passed to the system with only its name. Its population can be retrieved using an external service that, given a name, will return the number of inhabitants.
- In the storage system, cities should be already enriched with the population. However, if the population is not known, they can be stored temporarily for a later enrichment.
- When asking for the list of cities, they should be retrieved in alphabetical order, omitting any incomplete city (those without a population).
Let’s start coding.
Setup and Required Dependencies
I created the sample code using the TPD Basic Maven Archetype since creating a POM file from scratch is boring. To use the archetype you just need Maven. Using the command line, execute:
To replicate the code exactly as it is on GitHub, use
com.thepracticaldeveloper as groupId.
The Basic Java Archetype comes with some useful testing dependencies, which are actually the ones we need for this example: JUnit, Mockito, and AssertJ.
pom.xml file we also get Unit Test Coverage Reports (via JaCoCo) and JAR packaging. All we need to start.
Note that to use BDDMockito you don’t need any extra dependency since it’s just a class inside the core Mockito library containing method stubs.
To cover the requirements, we’ll need to model the City in the code, and also some actions. Let’s start with the simple POJO:
Nothing fancy there, just an immutable class with getters,
equals, and a method to copy the city with a new
Let’s create now two interfaces to model other functionalities apart from the main logic: the external service providing the city population (
PopulationService) and the persistence layer to store and retrieve our cities (
The good thing about Test-Driven Development (TDD) and Behavior-Driven Development (BDD), is that we don’t need to implement those pieces of functionality to start verifying our app requirements (or the app behavior if you want to use BDD terms). That’s a great superpower: let’s focus first on the main requirements we got so you can validate and change them if needed (in a real scenario with your team, business analyst, etc.).
The code fulfilling the requirements is inside a
CityService class, which is interacting with the other two interfaces and adding some logic on top of it:
Let’s go through it, just in case you got some questions:
- The constructor’s arguments are the interfaces needed within this service. As you can imagine, with some annotations you could use a dependency injection framework here.
enrichAndCreateCitymethod covers one of the main requirements and allows to create a city with only a name in it. It calls the
PopulationService, enriches the city by making a copy of it, and finally stores it using the
CityRepositoryinterface. This method won’t work yet, given that we don’t have implementations of those interfaces, but we can test it perfectly since the interfaces are clearly defined. There is also a very low-level validation there, which will throw an exception in case the city is passed with an id. I kept it like that for simplicity but, in real life, you could use a validation library which comes with some predefined annotations (for instance Hibernate or Spring).
- Finally, the method
getAllValidCities()retrieves all stored cities and returns a filtered, sorted list as per the requirements. This is something you could do directly as a query in a standard database, but let’s add it here, knowing that we don’t have any functionality to storing/retrieving anyways.
You can go ahead and try to code this yourself, or you can also clone the GitHub repository and check how tests work by running, from the project folder:
You can also use your preferred IDE which surely provides a more visual way to check the results.
BDDMockito and AssertJ: Basics
It’s time to have a look at the Unit Test so you see where are the benefits. First, we’ll create a test method to verify that creating a city works as expected.
Let’s review first some basic JUnit and Mockito configuration that we need as usual:
@Testannotation tells JUnit that the method is actually a test, so it should be executed like that.
@RunWithis an optional JUnit annotation that we use here to replace the standard runner for
MockitoJUnitRunner. This has some advantages like automatic Mock initialization (just by using the
@Mockannotation), detection of unused stubs and validation of incomplete stubbing.
@Beforeis also a JUnit annotation and tells the framework to execute that method before each test method. We use it here to initialize the service to test and to configure the city representation, but we’ll cover that a bit later.
@Mockannotation is just another way to configure an object that will have the same methods available, but that we can configure to behave as we want by telling Mockito what to do when methods are called. This is a very brief explanation so, if you want to know more about Mockito basics, I recommend you to have a look first at an introductory tutorial.
Given, When, Then
As you see in the code above, the test is structured in three parts: Given, When and Then. I added comments for a better visualization but this is something you can skip when you get used to it.
- The Given part entitles the test case setup. The assumptions, the preconditions, the requirements for this use case.
- The When part is the action that you want to test. Normally, it’s also the smallest part of the test since the execution we want to test is typically one or two lines of code.
- The Then part is used to very what should happen after the execution of the action, which is represented usually by assertions to mocked classes and validation of returned results.
What we’re doing here is writing our test cases following a natural language approach as introduced by BDD (Given this preconditions, When this action happens, Then these results should be obtained). And that comes with serious advantages:
- This natural language is easy to read not only by other developers but also by business analysts. That means they can extract what the tests are doing and help to refine new scenarios and cases. Furthermore, if you start using this approach the idea is that you can eventually move from a starting point in which developers are writing the scenarios (Given, When, Then) to a much better way of working in which the business analysts are documenting the features using this syntax: that’s the real superpower that you may get with this small change. If you want to see a more advanced example of BDD Testing using Cucumber, save this other article for a later reading: End-to-End Microservice Tests with Cucumber
- Structuring your test classes in this easy way creates a soft, implicit convention that, if everybody follows and gets used to it, makes much faster to read what someone else’s tests are doing, which parts are mocked and what are the assertions. When tests fail or need additional functionality, maintenance becomes easier if you know where to fix or insert the code.
We don’t need to go for a full Gherkin syntax to start with and at this level (especially not for Unit Tests, that should be as simple as possible). All we need is to structure our code. So, you might be asking yourself, what is BDD Mockito and what are the differences with standard Mockito? It’s very simple, the standard Mockito implementation uses the following syntax:
And that when there to set up the scenario (configure the mocks) is annoying for BDD purposes since When is used to trigger the action, not to set up the scenario. That little thing might be confusing so the great team behind Mockito decided to create a class with BDD-friendly aliases,
BDDMockito. So you can now configure your mocks like this:
It might look like a very subtle difference, but this last piece of code is much better for our goal.
In the code sample above (also extracted below for reference) you can see two examples of BDDMockito, the first using
willReturn and the second one the more flexible
willAnswer, which allows us to capture the argument to use it to generate our own result:
Great! Now we have a good way to specify our scenarios in a BDD style using full-power Mockito. What about the remaining parts?
As you can imagine, the When part is so simple -just calling the method you want to test- that is not worth it to add anything there. Just call your method and store the result for later checks. What I recommend you here is to separate this method call with a blank line before and after so it’s easily recognized. You can also add the
//When comment line if you have a big chunk of code before and/or after.
So that leaves us with only the Then part to be figured out. Let’s see how AssertJ can help us there.
Readable and Powerful Assertions with AssertJ
Why should you care about AssertJ if JUnit comes with assertions out of the box? Actually, JUnit assertions are quite limited to a few basic scenarios and -in my opinion- lead to confusion when reading them. For example, I’ve been coding years with the JUnit API for assertions and I still mix up the actual with the expected value…
AssertJ, apart from being fully compatible with JUnit, brings many benefits to your unit tests. Two important ones:
- AssertJ comes with a lot of possibilities to execute assertions: elements in lists in any order, equality excluding specific fields, support for
Optional, extraction of values, custom representations, etc. This makes your life easier since you don’t have to preprocess the results to prepare them for the assertions; the assertion methods take care of that for you.
- Assertions are much more readable (being this a bit subjective). You can judge yourself but, to me, there is a big difference in readability between these two versions:
assertEquals("Check that City ID is set when stored", inputCity.getId(), actualCity.getId())(JUnit version, either you use meaningful variable names or it’s difficult to remember actual and expected)
assertThat(actualCity.getId()).as("Check that City ID is set when stored").isEqualTo(inputCity.getId())(AssertJ – Much more natural, fluent API). Note that, in this post, we’ll use
then, the BDD alias of
We’ll complete the missing part in this guide (the Then in Given-When-Then) with AssertJ assertions. You already saw that in our first test method
createCity(), where we use two:
Vanilla AssertJ uses the method
Assertions.assertThat(actual) (abbreviated as
assertThat(actual) to create the assertion type using the static import). But AssertJ also has a BDD-friendly alias (like BDDMockito has):
BDDAssertions.then(actual). Again, they behave exactly the same way and this is just a useful shortcut that we’ll use to make even more visible the distinction between the Given-When-Then parts.
You can chain several assertions with some other useful methods in a fluent way after
then(). In my case, I like using
as(description) to provide a better error message in case the assertion fails. It adds even more readability to your tests as well, since they are at the same time your test documentation. You can also add the description in terms of
"City ID should not be null when stored" if you prefer (actually, I prefer that as you’ll see in further examples).
isEqualToIgnoringGivenFields is very convenient for cases like the one shown above. You have a plain java object that gets enriched in your service layer so you want to verify that the process worked fine, but you better avoid comparing field by field or creating yet another object just to use it as a reference for comparison. This method will ignore the passed field when checking for equality (therefore, AssertJ will ignore your
In the next section, I’ll explain some other common, useful assertions coming with AssertJ that you can use in your Unit Tests depending on your scenario.
Overview of some useful AssertJ assertions
One of the best things about AssertJ is its documentation: full of practical examples. I check it everytime I’m wondering if there is an assertion for a scenario and almost always find it there.
What I’ll do in this article is putting some of them in practice in our sample code so you see a closer-to-real application of them.
Remember our requirements? We still need to write a couple of tests more:
- Check that an exception is thrown when the input contains an ID (validation test).
- Check that the list of cities only contains those with population.
Below you find the full version of the test, and then the description of the main features used.
Flexible equality assertions
We already saw how
isEqualToIgnoringFields can save us some time and code lines, but you also have some other alternatives for equality comparison:
isEqualToComparingFieldByFieldis very useful when you deal with objects for which you’re not in control of (or simply don’t want to add) the
equals()implementation. Two different objects with equal field values will result in being equal with this method, while they would be asserted as different when using the standard
isEqualToassertion. There is also a recursive version of this one,
- You can also decide to ignore fields from comparison with
isEqualToIgnoringNullFields. However, I don’t use this one too much since it’s risky: a field may be null for an erroneous reason and it’s better not to ignore that silently.
Clean-and-clear assertion of Exceptions
In our second test method above,
createCityWithIdWillThrowException we want to verify that an Exception is thrown. AssertJ has a smart alternative to deal with this by leveraging functions in our code. What we use is a function and then pass that function as an argument to the method
callThrowable(), that will call it and capture the exception for us.
Easy handling of Soft Assertions
When an assertion fails, the test case is interrupted and this one is the only one you’ll see in the output. Sometimes it’s useful to let the test continue and verify some other results since there might be a few things failing and you want to fix all of them together, avoiding a fix-one, next-error, fix-one situation.
This is achieved in an easy way with
SoftAssertions in AssertJ, and you saw it in the test to check the list of cities:
I prefer this functional-style amongst the multiple options to use SoftAssertions: you don’t need to call
softly.assertAll() and the block is nicely delimited by the function. There is even a
BDDSoftAssertions that you can use, but it’s stateful so it doesn’t allow this way of coding.
If you want to see how the soft assertions work, comment the line in
CityService that is taking care of null population filtering and run the test (as indicated in the code comment). You’ll get this error:
How cool is that?
Custom Object Representations
Did you notice that the
City class doesn’t have a
toString() method? That’s annoying. You could simply add it but imagine that the class is inside an external library and you can’t access the code. Without any additional code, instead of the error shown above, you would get this one:
Awful. How are you supposed to know which city is
[email protected]? You better use a custom representation. AssertJ allows you to do that in a simple manner by extending
StandardRepresentation and providing your own string representation for the objects you want by overriding the method
fallbackToStringOf(object). So you can code this class to provide a JSON representation of City (why not?):
Then, all you need is to tell AssertJ to use this new representation, which we included in the previous code inside the
I hope you enjoyed this guide and learned something new. As you saw, just with some basic code conventions you can improve a lot the way you write your Unit Tests. The adoption of BDD, even at a minor scale, can be an eye-opener for the rest of people, who can quickly find its benefits and maybe drive the change to a full-BDD environment. Also, we covered how AssertJ assertions can help you write more readable verification code blocks and learned some common usages.