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 2.6
Cucumber 7.6
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 😀
Introduction
JaCoCo est un outil très connu dans le monde Java qui permet de générer des rapports de code coverage au format xml et html.
L’intérêt sera souvent de donner le rapport au format xml à d’autres outils (Codecov ou Sonar par exemple) pour suivre le code coverage de votre projet.
Nous allons voir pas à pas comment configurer Jacoco pour qu’il génère un rap...
Introduction
Dans nos applications, il y a souvent des objets qui dépendent les uns des autres (un Service avec des Repository par exemple). Si on devait satisfaire ces dépendances “à la main”, on devrait les instancier, vérifier que ça n’ait pas ...
Introduction
Chiffrer et déchiffrer des informations est un besoin qu’on rencontre souvent dans nos projets et Java donne nativement une palette d’outils pour y arriver. On va voir dans cet article comment réaliser ces deux opérations via un cas prati...
Il n'est pas évident de savoir quelle est la version de maven minimale conseillée pour une version de Java car la documentation maven de le précise pas.
On sait quelle est la version minimale de JDK pour chaque version "Maven 3.8+ requires JDK 1.7 or above t...