Le seul problème de Cucumber (à mon sens) est qu’il n’est pas évident à configurer.
Après l’avoir mis en place sur plusieurs projets, je vous propose de prendre un raccourci et voir pas-à-pas comment l’installer simplement sur votre projet 🙂
Set Up Technique
Pour la démo, j’ai utilisé:
Java 17
Spring boot 3.1
Cucumber 7.14
Junit Jupiter 5.9
Mes choix
Dans le cas d’illustration, je souhaite lancer mon application avant mes tests afin de ne pas mocker le contexte Spring
Je fais ce choix car je suis plus proche de la réalité de cette façon. Il faut cependant bien garder en tête que le temps d’exécution des tests est plus long. C’est pour cette raison qu’il m’arrive de le mocker dans d’autres projets mais nous ne le verrons pas dans cet article.
Objectif
On a une API réalisée avec Spring Boot super simple qui a 2 routes:
On veut s’assurer que l’utilisateur peut bien publier un Kata dans notre catalogue de Katas.
Feature: Adding a Kata to the catalog
Scenario: A user publish a new Kata
Given A user publish a new Kata named AperoTech
When A user asks for the Kata list
Then He should have a success response
And The following kata list should be in the response content
| name |
| AperoTech |
Ce dont on a besoin
1. Importer les dépendances nécessaires.
2. Définir un Runner qui va exécuter nos tests.
3. Définir la configuration Spring à utiliser.
4. Ecrire des Steps.
5. Ecrire la feature.
Et ça sera déjà pas mal 😀
Les imports nécessaires
Dans votre pom.xml, ajoutez les dépendances suivantes:
Le Runner est la classe qui va se charger d’exécuter vos scénarios Cucumber. On doit lui indiquer comment exécuter les tests (via Junit Platform dans notre cas), où sont nos features, nos steps et notre configuration Spring.
On va donc créer une classe CucumberRunnerTest dans un package cucumber à la racine de nos tests: test/java/cucumber avec le contenu suivant:
package cucumber;
import org.junit.platform.suite.api.ConfigurationParameter;
import org.junit.platform.suite.api.IncludeEngines;
import org.junit.platform.suite.api.SelectClasspathResource;
import org.junit.platform.suite.api.Suite;
import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;
@Suite
@IncludeEngines("cucumber")
@SelectClasspathResource("features")
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "cucumber")
public class CucumberRunnerTest {
}
On décortique la configuration:
@Suite
@IncludeEngines("cucumber")
Ici, on dit d’utiliser la plateforme Junit pour exécuter nos scénarios.
@SelectClasspathResource("features")
Je dis ici que mes fichiers .features seront situés dans le dossier features de mes ressources de tests.
@ConfigurationParameter(key = <em>GLUE_PROPERTY_NAME</em>, value = "cucumber")
Là on indique que les Steps et la configuration Spring seront à chercher dans le package cucumber.
Note: J’ai appelé ma classe CucumberRunnerTest pour pouvoir lancer mes tests via la commande mvn test mais ce n’est pas obligatoire
La configuration Spring
Comme mentionné plus haut, j’ai fait le choix de lancer le vrai contexte Spring pour mes tests. Mon applications se lance donc réellement:
Créez un package configurations dans votre package cucumber et créez la classe suivante:
package cucumber.configurations;
import com.aperotech.AperotechApplication;
import io.cucumber.spring.CucumberContextConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
@CucumberContextConfiguration
@SpringBootTest(classes = AperotechApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CucumberSpringConfiguration {
}
Là seule subtilité ici est de bien définir un port disponible aléatoire sur lequel va s’exécuter l’application (SpringBootTest.WebEnvironment.RANDOM_PORT)
Créer la feature
Un fichier .feature est constitué d’un ou plusieurs scénarios qui sont une successions de Steps articulés par des mots clés (Given, When, Then) cf. https://cucumber.io/docs/gherkin/
Il faut créer un dossier features dans votre dossier resources du dossier de test et créer un fichier .feature. Je l’ai appelé le fichier kata.feature dans mon cas.
Feature: Adding a Kata to the catalog
Scenario: A user publish a new Kata
Given A user publish a new Kata named AperoTech
When A user asks for the Kata list
Then He should have a success response
And The following kata list should be in the response content
| name |
| AperoTech |
Définition des Steps
On a vu dans le paragraphe précédent qu’on voulait utiliser des steps commeHe should have a success response.
Il faut définir dans une ou plusieurs classes la définition de ces steps, c’est-à-dire le code qui va être exécuté lorsque cette step sera lue.
Si on reprend la step utilisée en exemple, le code exécuté sera:
@Then("He should have a success response")
public void heShouldHaveASuccessResponse() {
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
}
Le lien entre la step et la méthode exécutée se fait via l’annotation (@Then("He should have a success response")). Ensuite on utilisera une assertion comme pour n’importe quel test unitaire.
Il faut créer une classe qui va contenir les définitions de nos Steps.
package cucumber.steps;
import com.aperotech.kata.domain.Kata;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.cucumber.datatable.DataTable;
import io.cucumber.java.Before;
import io.cucumber.java.en.And;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import io.restassured.RestAssured;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
import net.minidev.json.JSONObject;
import org.apache.http.HttpStatus;
import org.springframework.boot.web.server.LocalServerPort;
import java.util.List;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
public class KataStepDefs {
private static Response response;
@LocalServerPort
private int port;
@Before
public void setUp() {
RestAssured.baseURI = "http://localhost";
RestAssured.port = port;
}
@When("A user asks for the Kata list")
public void aUserAsksForTheKataList() {
RequestSpecification request = RestAssured.given();
response = request.get("/kata");
}
@Then("He should have a success response")
public void heShouldHaveASuccessResponse() {
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
}
@Given("A user publish a new Kata named {word}")
public void aUserPublishANewKata(String name) {
RequestSpecification request = RestAssured.given();
JSONObject requestParams = new JSONObject();
requestParams.put("name", name);
response = request
.header("Content-Type", "application/json")
.body(requestParams.toJSONString())
.post("/kata");
}
@And("The following kata list should be in the response content")
public void theFollowingKataShouldBeInTheResponseContent(DataTable table) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
String serializedKata = response.getBody().asString();
List<Kata> actualKatas = mapper.readValue(serializedKata, new TypeReference<>() {
});
List<String> expectedKataNames = table.asList().subList(1, table.height());
for (String expectedKataName: expectedKataNames) {
boolean nameFound = actualKatas.stream()
.anyMatch(kata -> kata.getName().equals(expectedKataName));
assertThat(nameFound).isTrue();
}
}
}
La première partie permet simplement à RestAssured d’utiliser le bon port pour ces appels:
@LocalServerPort
private int port;
@Before
public void setUp() {
RestAssured.baseURI = "http://localhost";
RestAssured.port = port;
}
Si vous avez peur que les appels soient trop lourds, MockMVC est un outil tout à fait viable que j’utilise également.
Lancer le test
Si vous avez bien suivi ce tutoriel, un mvn test ou exécuter le Runner via votre IDE
Devrait lancer toutes vos features 🙂
Note: IntelliJ ne trouve pas automatiquement la Glue si on exécute directement la feature, il faut changer la Glue par défaut par cucumber au lieu de steps:
Conclusion
J’espère que ce guide vous aura permis de configurer facilement Cucumber et que ces tests vous permettront de gagner en confiance sur votre application 😀
Bonjour Maxime,
Merci beaucoup pour cet exemple, pour ton travail et ce partage !
J’ai actuellement un pb avec Cucumber, Spring-Boot 3.1.5, Java 21, WebFlux et l’utilisation de TestContainer.
Pour le moment j’ai une exception : Failed to process import candidates for configuration class [org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration]: Error processing condition on org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryConfigurations$PoolConfiguration
N’aurais tu pas déjà réalisé une configuration Spring Cucumber et Tests container par hasard ?
Merci beaucoup d’avance
Rudy
Malheureusement je n’ai jamais utilisé de tests container du coup je ne suis pas en mesure de répondre à la question dsl 🙁
Merci pour ton commentaire, j’espère que tu as pu trouver une solution.
N’hésites pas à la mettre en commentaire si jamais ça peut aider d’autres personnes.
On va voir comment avoir en quelques minutes des assertions qui vont vérifier les endpoints de notre API avec des scénarios de ce genre:
Feature: Create a new account
As a visitor,
I can create an account to access the game
Scenario: A visitor creates an account
When I fill the login form with
| email | password |
| [email protected] | Jh0...
Depuis .NET 9, le le support d’OpenAPI est directement inclus dans .NET et ne passe plus par les librairies Swagger par défaut (plus d’info sur ce choix ici si jamais ça vous intéresse).
De façons simplifiée, la librairie Swashbuckle.AspNetCore.Sw...
Description du problème
Par défaut, il n’est pas autorisé de faire des requêtes entre une application qui est dans un domaine A vers une autre qui serait dans un domaine B (pour des raisons de sécurité, il y a plus de détails dans les sources).
S...
On va créer pas-à-pas un raccourci qui nous permettra de générer un IBAN et de l’insérer à l’endroit où se trouve notre curseur. Si vous n’avez pas fait le setup pour développer un plugin Jetbrains, je vous invite à lire notre article sur le sujet.
...
Bonjour Maxime,
Merci beaucoup pour cet exemple, pour ton travail et ce partage !
J’ai actuellement un pb avec Cucumber, Spring-Boot 3.1.5, Java 21, WebFlux et l’utilisation de TestContainer.
Pour le moment j’ai une exception : Failed to process import candidates for configuration class [org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration]: Error processing condition on org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryConfigurations$PoolConfiguration
N’aurais tu pas déjà réalisé une configuration Spring Cucumber et Tests container par hasard ?
Merci beaucoup d’avance
Rudy
Bonjour,
Malheureusement je n’ai jamais utilisé de tests container du coup je ne suis pas en mesure de répondre à la question dsl 🙁
Merci pour ton commentaire, j’espère que tu as pu trouver une solution.
N’hésites pas à la mettre en commentaire si jamais ça peut aider d’autres personnes.
Bonne journée,
Maxime