Javarevisited
Published in

Javarevisited

Cucumber & Testcontainer: a BDD perfect match

In some project, the need of an automated and powerful test layer is seen as something not really useful for the good outcome of the project…

Luckily, there are many projects where the test phase is significant, with proven positive consequences for the general quality of the source code. We have so many frameworks, libraries and methodologies to use. In this article, we will see how the use of two tools, Cucumber and Testcontainer, helps us to create a perfect environment for our tests.

Behavior Driven Design

Different types of tests can be performed on our software, and obviously, each of these can be implemented with different techniques and tools

A famous approach is TDD (Test Driven Design) which can be summarized (really shortly) by defining a test case for our software and then implementing software that satisfies it. The test flow will be the following

  • Test definition
  • Test implementation
  • Test execution : first execution will fail, because our software doesn’t satisfy it
  • Software implementation
  • Test execution without problems

Another great approach in test development is BDD (Behavior Driven Design). In that case, we must define, using a simple domain-specific language, the desired behavior of our software.

To define a test scenario, we can use a simple language based on the keywords Given When Then

A simple scenario can be the following one

This language syntax (Gherkin) helps us define scenarios that can describe the behavior of our system. Test frameworks, like Cucumber, read this feature’s description and try to execute it.

Cucumber: setup

Cucumber is a test framework available for the most important programming languages. Using it we can map a feature description written in Gherkin to some test code implementations.

This article will use Cucumber-JVM, in a Spring Boot project with JUnit 5 as principal test engine. The first thing we can see is the dependencies to add in our pom.xml

The example project will provide simple REST endpoints to manage two resources related to a blog engine: authors and posts. Let’s define some scenarios that our API will satisfy using the Gherkin language.

Some scenarios in a first feature file about author resource

and some others related to post resource

Now we must start telling our project it must use Cucumber, and the scenario definitions will be available under the src/test/resources/bdd folder

@IncludeEngines annotation is to specify the Cucumber engine for our tests and @SelectClasspathResource must be used to add our feature files.

Among the other settings, using @ConfigurationParameter, we ask Cucumber to produce an HTML test execution report.

If we start the build now, without any source definitions, we should receive an error like this

We can start with implementing the test cases, but before doing it, let’s look at REST Assured, a powerful library that can be our swiss army knife for the test cases implementation.

REST Assured

If you must develop all the test cases using some Java libraries, like Spring RestTemplate/WebClient or something else, you will probably create a lot of boilerplate code. REST Assured offers a fluent syntax to develop powerful but simple test code implementations.

To add the library you must import these dependencies

Imagine you have a “colors” REST endpoint, providing a list of colors like this

["red", "green", "blue"]

using REST Assured you write a simple test case like this

or if you want to submit something, in a scenario similar to our test project, you can write this test case

Its fluent API can be used to develop quick test cases like these or, as in our project, to bind these methods to our test scenarios.

Cucumber: test implementation

We can start our test cases implementation with some common steps

This class is in the package that Cucumber will scan to find the step implementations, as previously defined in our setup. As you can see, @Then annotations are bounded to Then keywords described in some scenarios.

In addition to this, we created an utility class, CucumberContextHolder, that can be useful saving some test context information. Now we are using it to keep the response from REST Assured calls.

In these steps, we are simply checking the HTTP status code. Now we go further to the other actions defined in this scenario

Scenario: 001 - Save author OK   Given a username 'federico'
And an email 'federico.paparoni@xyz.mail'
And a bio 'Federico Paparoni wants to write something about Cucumber & Testcontainer'
When I submit this information to save a new user
Then I receive a correct response

and this is the implementation

In these steps we are creating an author (Given steps) and then sending it to the REST endpoint using REST Assured (When step).

Using the same approach we can implement all the steps, also trying to build something that isn’t available in our system. For example this step

Given a system without the author with username 'goofy'

requires an author already saved in our blog engine. So we can suppose that our database already has this author, or we can save it during the setup of the Given step. I prefer the latter choice, so I implemented this condition in that way

Gherkin syntax offers a lot of features like the option to insert list of values in the scenario (DataTables). If you have repeated steps like these

Feature: My feature   Scenario: 001
Given a username 'federico'
And an email 'federico.paparoni@xyz.mail'
And a bio 'Federico Paparoni wants to write something about Cucumber & Testcontainer'
...
...
Scenario: 002
Given a username 'federico'
And an email 'federico.paparoni@xyz.mail'
And a bio 'Federico Paparoni wants to write something about Cucumber & Testcontainer'
...
...
Scenario: 003
Given a username 'federico'
And an email 'federico.paparoni@xyz.mail'
And a bio 'Federico Paparoni wants to write something about Cucumber & Testcontainer'
...
...

you can use the Background keyword

Feature: PostsBackground: Author already saved
Given an author with username 'federico' already saved
And a post by author with username 'federico' already saved
...
...

These steps will map to Java methods as the previous seen, but Cucumber will repeat it for every scenario of a single feature

Testcontainer

Once you create tests with Cucumber, you can use a database like H2, but you won’t realize something more sophisticated than a Hello World. In an actual project, you will probably use some particular database feature (relational or not relational), so your test case will not work in a realistic environment.

Testcontainer provide us a simple way to use containers inside our tests, so we can integrate with a database, a browser, a queue messaging system or everything that can be placed in a container.

In this article we will use Testcontainer to start a PostgreSQL database, defined by this Dockerfile

We wrote also some scripts to create some tables for our blog engine software. To start build you can launch the following command

docker build -t bddfun-database .

Now we can add Testcontainer to our project

To launch Testcontainer, we can use three different approaches. The simplest is to use a particular JDBC URL that will trigger a kind of magic in the JDBC DriverManager, and without so much effort, we will have a running instance of PostgreSQL

spring.datasource:
url: jdbc:tc:postgresql:14.3:////<DatabaseName>
driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver

This instance will be a plain one, so we must manage scripts in our codebase and launch them. Another approach is @Testcontainers and @Container annotations

@DynamicPropertySource will dynamically save the database properties in our context, and we will have a different PostgreSQL, which will start from our bddfun-database container, running for every single test.

It can be helpful to have a completely new database for every single test, but for a project with many test cases it can extend the build time too much.

The last option, chosen for this article, will provide us a complete start/stop control of a container for our tests

Using this approach we also applied the @CucumberContextConfiguration to this class, so Cucumber knows that this will be the context used for our tests. Our project is using Spring Boot so we also added @SpringBootTest annotation.

This configuration gives us a single instance of our custom database, managed by the methods with @BeforeAll and @AfterAll Cucumber annotations (similar to JUnit).

Now running the build, if we also remember to develop the REST endpoints of our blog software (!!!), we should have the following report (cucumber-reports.html) generated under target folder

Bonus track: Mockito

Our system will probably be something more sophisticated than a simple Spring Boot application using a database, so we must suppose our test cases should provide an easy way to mock external system.

If we have a container of our external system we can use Testcontainer, but sometimes this approach can lead to a lot containers running in our build (many containers, lot of memory, huge building time..).

Mockito is a testing framework and can be integrated in this testing scenario with Cucumber and Spring Boot. If we want to mock a service we can do it with the @MockBean annotation

@MockBean
private MyRemoteService myRemoteService;

This mock must be placed in the same class where we put @CucumberContextConfiguration, so Cucumber will know we are using a mock for this service.

Later in our test we can mock the service

Example project

Source code of this article can be found in this Github repository

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store