End-to-End Microservice Tests with Cucumber
Table of Contents
If you work with Microservices, you surely faced the situation in which some of your system processes only make sense after the information flows through several microservices. For instance, if you’re implementing an Event-Driven architecture, you certainly want to verify your Business Processes when events are propagated and other parts react to them. There are several frameworks you can use for that but, for its simplicity and perfect matching with Business requirements, Cucumber is a great option. Let’s get into the process of writing end-to-end scenarios using Cucumber.
NOTE: this post doesn’t cover the use of Dependency Injection with Cucumber in Spring Boot. However, I’ve posted another article with a code sample to explain how to achieve that: Cucumber Tests in Spring Boot with Dependency Injection
What is Cucumber?
Cucumber is a framework intended for Behavior-Driven-Development (BDD) that supports many different programming languages. BDD is an approach that defends writing first the requirements in human language (Behavior) and, only then, developing the code making it cover the described behaviors. To accomplish this, Cucumber created Gherkin, a DSL that can be interpreted by the framework, building a powerful bridge between Business Users -using Gherkin to describe the requirements- and Developers -implementing the code behind every sentence of that human-understandable language. Let’s see an example of a Gherkin test definition:
How does it work?
The idea is that we organize our features into multiple
.feature files (like the one above). In each of them, we include the description of the feature on the top. That description is going to be ignored by the engine. Every feature consists of multiple scenarios, which technically are your different test case definitions. Finally, each scenario is defined by multiple steps, using the BDD keywords: Given, When, Then (plus And and But).
Each scenario (or test case definition) will be executed within its own fresh context. This concept is very important to understand to implement our tests correctly: we can share the state between steps of the same scenario, but not across scenarios.
The steps can be parameterized using arguments, so we can reuse the same step definition in multiple scenarios with different values, or we can also pass a data table to the same scenario (which rows are called examples). In any case, every example case will be executed in a new context.
Mapping Scenarios to Code
Let’s use this step to understand how arguments work:
In principle, Gherkin itself does not know which of our words are arguments. We define that at the code level. In this case, we’d like to pass to our step the user alias, the number of attempts and if they are right or wrong. Let’s have a look at how this step needs to be implemented in Java to support our arguments:
We’ll have a look at the coding details in the following section, but as you can see it’s just a matter of configuring our step with some regular expressions, which will match our words in the sentence. Two word-expressions and a numeric one will do the trick in this particular case.
Combining Cucumber with other tools
Now that we know how to link the Features to Java code, how do we continue? Keep in mind that Cucumber is not giving you a full testing framework for Java, it’s just the link between your test definitions and code. To make our scenarios work, we should include in our project some other useful tools. In our case, we’ll introduce:
- JUnit: since it’s giving us the core testing support for Java.
- AssertJ: I prefer its way of asserting results, but you could also use the JUnit style.
- Apache Fluent HttpClient: because we want to keep it lightweight and at the same time have a straightforward way to perform requests to our microservices.
- Jackson 2: we want to (de)serialize JSON requests/responses.
Note that, even though we’ll use Spring Boot for our microservices, we don’t need to use it for our test system.
Coding the solution with Java and Cucumber
It’s time to get into the code. I’ll use an existing microservices application, the one included in my book’s Learn Microservices with Spring Boot repository. If you’re wondering how the application works you can benefit from Cucumber and read the feature included above (cool!).
The best way for you to get a good understanding is to clone or download that repository and experiment with it (don’t forget to give it a star!).
So, let’s assume you have the microservices application up and running. We want to check the different cases in which the user posts HTTP requests to one of the services (via UI and the gateway), then that service triggers an event, and a different microservice will consume it and perform some extra operations. It’s indeed a typical End-to-End microservice test suite, exercising an Event-Driven architecture.
The E2E project is structured as shown in the picture:
- Our JSON expected responses are modeled as simple beans (
- The main bridge with the application is the
MultiplicationApplicationclass. We use it to make our requests, etc., from the functional perspective (e.g. send an attempt instead of send HTTP POST).
- The technical implementation to contact with our app is developed in
ApplicationHttpUtils, which uses Apache Fluent HTTP.
Then we have the main classes modeling our tests (we have two features thus two tests):
MultiplicationFeatureTest. They are just entry points for the Cucumber runner to find the right
.feature file, which are placed in the
resources folder. We use two annotations here:
@RunWith passing the Cucumber runner, and
@CucumberOptions to specify the feature we want to execute and the reporting capabilities – we want to generate a JUnit-compatible report.
Having understood all this, the only piece we miss is the implementation of the steps. We can complete the full story now and check how the steps trigger the Java methods within a fresh context per scenario, and how assertions make their part.
For the curious ones, two notes:
- Did you notice the thread pausing at some point of our test? It’s there because our system is Eventually Consistent: the event will take some time to be consumed by the second microservice, and that one will take its time to complete the operation. The
sleep()methods are there just to illustrate the eventual consistency, make sure you don’t use those waits in a real CI system: they can become easily the source of many spurious errors (e.g. if you have a congested CI machine that doesn’t respond in time). Instead of this, you should implement a retry mechanism with a longer timeout.
- If you have a look at the
LeaderboardFeatureTest, you’ll see how Cucumber steps can be reused across features using the
cucumber-picocontainerdependency. It’s pretty straightforward to understand: one of the beans in the (pico)context is injected in the other one.
Note that this approach tests the microservices from an external project that is not using Spring Boot. If you want to see an example of how to use Cucumber inside a Spring Boot application (e.g. for Integration Tests inside the service) you can have a look at this other post: Cucumber Tests in Spring Boot with Dependency Injection