Cucumber & Testcontainer: un match perfetto per BDD

Durante lo sviluppo dei nostri progetti, l’implementazione dei casi test viene in alcuni contesti relegata ad un’attività non necessaria o comunque non vitale per l’ottima riuscita del progetto.

Per fortuna esistono invece molti progetti dove invece l’attività di test ha un peso considerevole (con evidenti conseguenze positive per la qualità generale del codice prodotto) e proprio per questo motivo esistono svariati approcci che possiamo adottare. In questo articolo vedremo come l’utilizzo di due differenti tool, Cucumber e Testcontainer, possa permetterci senza troppi sforzi di creare un setup perfetto per lo sviluppo e l’esecuzione dei nostri test.

Behaviour Driven Design

Prima di avventurarci nei meandri di svariati tool e configurazioni cerchiamo di capire quale potrebbe essere un approccio valido da seguire per lo sviluppo dei nostri progetti. Esistono diversi tipi di test che possono essere eseguiti sul nostro software e ovviamente ognuno di questi può essere implementato con tecniche e tool differenti

Una metodologia famosa è quella del TDD (Test Driven Design) che può essere riassunta (in maniera riduttiva) come cercare di definire prima un caso di test per il nostro software e successivamente implementare il software che lo soddisfa. Quindi in sequenza avremo

  • Ideazione di un caso di test
  • Implementazione del caso di test
  • Esecuzione del caso di test : la prima esecuzione fallirà in quanto il software ancora non lo soddisfa
  • Implementazione del software che soddisfa il caso di test
  • Esecuzione del caso di test (in questo caso positiva)

Un’altra famosa metodologia per la definizione dei test è quella che prende il nome di BDD (Behaviour Driven Design), dove andremo a definire esattamente cosa aspettarci in termini di comportamento del nostro sistema. Per modellare correttamente il comportamento del nostro sistema utilizzeremo una semplice descrizione degli scenari basata sulla sintassi dei costrutti Given-When-Then

Un esempio banale di uno scenario potrebbe essere il seguente

Feature: Blog searching
         I want to search articles in my blog 

  Scenario: Simple search     
    Given a browser with my blog      
    When I enter the "bdd" word in the search box and I press submit
    Then results for "bdd" are shown

La sintassi di questo linguaggio (Gherkin) ci permette quindi di definire una serie di scenari che possono descrivere il comportamento del nostro software. Esistono poi diversi framework che, prendendo in input questa feature, cercano di eseguirla in qualche maniera.

Cucumber

Cucumber è un framework disponibile per i principali linguaggi di programmazione e permette di avere a disposizione delle librerie che interpretano la sintassi Gherkin ed eseguono le azioni collegate alle diverse situazioni descritte nel nostro caso di test.

In questo articolo utilizzeremo l’implementazione Cucumber-JVM, all’interno di un progetto d’esempio che utilizza Spring Boot e JUnit 5. Per aggiungere le dipendenze relative a Cucumber possiamo semplicemente utilizzare il bom messo a disposizione ed includerlo nel pom della nostra applicazione

<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.junit</groupId>
			<artifactId>junit-bom</artifactId>
			<version>5.8.1</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
		<dependency>
			<groupId>io.cucumber</groupId>
			<artifactId>cucumber-bom</artifactId>
			<version>7.0.0</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>

L’applicazione d’esempio espone alcuni semplici endpoint REST per la gestione di due risorse relative ad un blog: autori e post. Andiamo a definire quindi una serie di scenari che le nostre API dovranno soddisfare, utilizzando la sintassi vista prima.

Pensiamo alla definizione delle funzionalità collegate agli autori

Feature: Authors

  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

  Scenario: 002 - Save author without username KO
    Given 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 an error
    
  Scenario: 003 - Get author KO
    Given a system without the author with username 'goofy'
    When I read the author with username 'goofy'
    Then I receive an error
    
  Scenario: 004 - Get author OK
    Given a system with the author with username 'pluto'
    When I read the author with username 'pluto'
    Then I receive a correct response

e poi descriviamo gli scenari relativi ai post

Feature: Posts

  Background: Author already saved
    Given an author with username 'federico' already saved
    And a post by author with username 'federico' already saved
    
  Scenario: 001 - Save post OK    
    Given the title 'Hello world'
    And the body 'Great article'
    And the author with username 'federico'
    When I submit this post to save it
    Then I receive a correct response

  Scenario: 002 - Save post KO
    Given the title 'Hello world'
    And the author with username 'federico'
    When I submit this post to save it
    Then I receive an error
    
  Scenario: 003 - Get post by author
    When I read the posts by author 'federico'
    Then I receive a correct response
    And all the posts are by 'federico'

Ora dobbiamo andare a specificare nei sorgenti dei nostri test qualcosa che permetta di utilizzare Cucumber e di leggere le definizioni degli scenari che avremo salvata nella cartalla src/test/resources/bdd del nostro progetto. Per fare questo basta scrivere la seguente classe

package com.javastaff.bddfun.test;

import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;
import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.File;

import org.junit.jupiter.api.Test;
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;

@Suite
@IncludeEngines("cucumber")
@SelectClasspathResource("bdd")
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty")
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "usage")
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "html:target/cucumber-reports.html")
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "com.javastaff.bddfun.test.glue")
class RunCucumberTest {
	@Test
	void testSuite() {
	    File bddResourcesDirectory = new File("src/test/resources/bdd");
	    assertTrue(bddResourcesDirectory.exists());
	}
}

Attraverso l’annotation @IncludeEngines abbiamo specificato a JUnit l’utilizzo di Cucumber e successivamente siamo andati a specificare dove andare a pescare le risorse con @SelectClasspathResource.

Infine con una serie di configurazioni abbiamo indicato a Cucumber che vogliamo un report in HTML e che troverà l’implementazione degli scenari nel package com.javastaff.bddfun.test.glue.

Se andassimo a lanciare la compilazione in questo momento vedremmo come JUnit invoca Cucumber, che non trova i casi di test e ci restituisce errori simili a quelli riportati

[ERROR] Authors - 001 - Save author OK  Time elapsed: 4.719 s  <<< ERROR!
io.cucumber.junit.platform.engine.UndefinedStepException: 
The step 'a username 'federico'' and 4 other step(s) are undefined.
You can implement these steps using the snippet(s) below:

@Given("a username {string}")
public void a_username(String string) {
    // Write code here that turns the phrase above into concrete actions
    throw new io.cucumber.java.PendingException();
}
@Given("an email {string}")
public void an_email(String string) {
    // Write code here that turns the phrase above into concrete actions
    throw new io.cucumber.java.PendingException();
}
@Given("a bio {string}")
public void a_bio(String string) {
    // Write code here that turns the phrase above into concrete actions
    throw new io.cucumber.java.PendingException();
}
@When("I submit this information to save a new user")
public void i_submit_this_information_to_save_a_new_user() {
    // Write code here that turns the phrase above into concrete actions
    throw new io.cucumber.java.PendingException();
}
@Then("I receive a correct response")
public void i_receive_a_correct_response() {
    // Write code here that turns the phrase above into concrete actions
    throw new io.cucumber.java.PendingException();
}

Dobbiamo quindi cominciare ad implementare i casi di test e riportarli nel package che abbiamo indicato. Per invocare le API REST che la nostra applicazione metterà a disposizione possiamo utilizzare una semplice e potente libreria: REST Assured.

Rest Assured

Implementare tutte le chiamate, i parametri, serializzazione e deserializzazione sicuramente ci portebbe a creare chili e chili di codice poco manutenibile e leggibile. REST Assured è una libreria che offre molte funzionalità utili per l’implementazione dei nostri casi di test.

Per aggiungere questa libreria al nostro progetto possiamo farlo inserendo le seguenti dipendenze

<dependency>
	<groupId>io.rest-assured</groupId>
	<artifactId>rest-assured</artifactId>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>io.rest-assured</groupId>
	<artifactId>rest-assured-common</artifactId>
	<version>${rest-assured.version}</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>io.rest-assured</groupId>
	<artifactId>xml-path</artifactId>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>io.rest-assured</groupId>
	<artifactId>json-path</artifactId>
	<scope>test</scope>
</dependency>

Per capire quanto può essere utile nella definizione dei nostri casi di test, immaginiamo di avere un endpoint REST colors che restituisce una lista di colori

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

Utilizzando REST Assured potremmo implementare un caso di test velocemente in questa maniera

@Test
public void whenRequested_thenListOK() {
    when().get("/colors")
          .then()
          .body("$", hasItems("red", "green", "blue"));
}

Oppure pensando ad un caso simile al nostro scenario possiamo pensare di verificare che l’inserimento di un nuovo autore vada a buon fine

@Test
public void whenRequestedNewAuthor_thenCreated() {
    with().body(new Author("federico","email@","bio"))
      .when()
      .request("POST", "/authors")
      .then()
      .statusCode(201);
}

Attraverso i suoi metodi fluenti possiamo eseguire velocemente dei casi di test oppure, come vedremo nel nostro caso, accoppiare questi metodi agli scenari BDD per poterli poi anche riutilizzare

Cucumber: implementazione dei test

Come abbiamo visto Cucumber, non trovando nessuno degli scenari descritti, ci ha suggerito cosa aggiungere nei nostri casi di test. Tutti i blocchi che andranno a definire gli scenari verranno pescati all’interno del package com.javastaff.bddfun.test.glue e come potete vedere dalle funzionalità ci sono alcuni step condivisi che andremo ad inserire in una classe comune

package com.javastaff.bddfun.test.glue;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;

import org.springframework.beans.factory.annotation.Autowired;

import io.cucumber.java.en.Then;

public class CommonSteps {
	
	@Autowired
	private CucumberContextHolder cucumberContext;
	
	@Then("I receive a correct response")
	public void i_receive_a_correct_response() {
	    System.out.println(cucumberContext.getResponse().asPrettyString());
	    assertEquals(200, cucumberContext.getResponse().getStatusCode());
	}
	
	@Then("I receive an error")
	public void i_receive_an_error() {
	    System.out.println(cucumberContext.getResponse().asPrettyString());
	    assertNotEquals(200, cucumberContext.getResponse().getStatusCode());
	}
}

Le annotation @Then si collegano esattamente agli step con il prefisso Then che abbiamo descritto nei diversi scenari. Oltre a quello abbiamo previsto l’esistenza di una classe d’utility, CucumberContextHolder, che andrà a memorizzare un contesto da condividere con diverse classi. In questo caso il contesto che viene condiviso è la risposta ottenuta dalla libreria REST Assured.

La verifica che viene fatta in questi due step è abbastanza semplice, visto che l’unica check è quello relativo allo status code http della response. Passiamo ora ad uno scenario che abbiamo visto in precedenza

  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

e passiamo quindi alla relativa implementazione

@Given("a username {string}")
public void a_username(String username) {
  authorBuilder.username(username);
}

@Given("an email {string}")
public void an_email(String email) {
  authorBuilder.email(email);
}

@Given("a bio {string}")
public void a_bio(String bio) {
  authorBuilder.bio(bio);
}

@When("I submit this information to save a new user")
public void i_submit_this_information_to_save_a_new_user() {
  String path = AUTHOR_ENDPOINT + "/";
  cucumberContextHolder.setResponse(
      given()
      .body(authorBuilder.build())
      .contentType(ContentType.JSON)
      .accept(ContentType.JSON).when()
      .post(path));
  // Reset for next build
  authorBuilder = Author.builder();
}

L’ultimo step, quello della verifica relativa alla risposta, è già implementato negli step comuni visti in precedenza mentre come potete vedere ad ogni step dello scenario viene associato un metodo che ricevendo anche parametri permette di costruire il nostro caso di test. In questo caso negli step Given andiamo a costruire un Author in base ai parametri che vengono indicati e infine nel When utilizziamo REST Assured per effettuare la richiesta e salvarci la risposta.

Seguendo questo stesso approccio andremo ad implementare gli altri scenari, cercando in alcuni casi anche di costruire delle informazioni che vengono richieste dal nostro test. Ad esempio il seguente step

Given a system without the author with username 'goofy'

prevede la presenza di un autore nel nostro sistema. L’implementazione potrebbe verificare la presenza dell’autore e nel caso non fosse presente provvedere all’inserimento

@Given("a system with the author with username {string}")
public void a_system_with_the_author_with_username(String username) {
	if (authorRepository.findById(username).isEmpty()) {
		Author author = Author.builder()
				.username(username)
				.bio("a bio")
				.email("email").build();
		authorRepository.save(author);
	}
}

La scelta in questo caso di inserire dinamicamente l’autore è stata nostra, magari si potrebbe supporre che il sistema debba avere alcune informazioni di test precaricate ma personalmente preferisco questa modalità che è più comoda a mio parere.

All’interno della sintassi Gherkin troverete molte caratteristiche interessanti come la possibilità di definire direttamente nel caso di test una lista di valori (DataTables). Tra le molteplici funzionalità offerte da Cucumber dobbiamo ad esempio quella relativa a degli step che vengono ad esempio eseguiti in tutti gli scenari di una certa funzionalità

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'

...
...

In questo caso possiamo definire una sorta di Background che sarà ripetuto automaticamente per tutti gli scenari che fanno parte di una funzionalità. Nell’esempio che abbiamo implementato ci sono due step che vogliamo ripetere per quanto riguarda la funzionalità dei post

Feature: Posts

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

Dal punto di vista del codice Java questo sarà trattato sempre con le stesse annotation, l’unica differenza sarà che Cucumber le richiamerà per ogni scenario definito nella stessa funzionalità.

	@Given("an author with username {string} already saved")
	public void alreadySavedAuthor(String username) {
		if (authorRepository.findById(username).isEmpty()) {
			Author author = Author.builder().username(username).build();
			authorRepository.save(author);
		}
	}
	
	@Given("a post by author with username {string} already saved")
	public void alreadySavedPost(String username) {
		if (postRepository.findByAuthorUsername(username).isEmpty()) {
			Author author=authorRepository.getById(username);
			Post post=Post.builder().author(author).body("body").title("title").build();
			postRepository.save(post);
		}
	}

Un’altra caratteristica comoda è quella che permette di avere la localizzazione per le keyword presenti nei feature file e anche con le relative annotation.

Per la lingua italiana andremo quindi a definire il feature file in questa maniera

# language: it
Funzionalità: Mia funzionalità

  Scenario: 001 – Uno scenario
  Dato qualcosa
  Quando succede un evento
  Allora ottengo un certo risultato

E in corrispondenza di queste keyword potremo poi utilizzare le annotation presenti in questo caso nel package io.cucumber.java.it.

Testcontainer

Una volta realizzati i test con Cucumber potremmo anche appoggiarci ad un database in memoria come H2 e fare qualcosa che leggermente oltre il semplice Hello World. In dei progetti reali avremo sicuramente molte informazioni che a tempo zero devono già essere presenti sul database, inoltre l’utilizzo di un database in memoria può essere si utile ma se dobbiamo verificare alcune caratteristiche peculiari di uno specifico db (relazionale o non relazionale) ci troviamo sicuramente a dover abbandonare questa semplice strada per i nostri casi di test.

Testcontainer è una libreria che ci permette di utilizzare container all’interno dei nostri test. Quando parliamo di container quindi ci possiamo riferire a database, browser, sistemi di messaggistica e in generale qualsiasi cosa riusciamo ad infilare dentro un container che possa essere collegata al nostro sistema.

In questo esempio ci siamo limitati ad utilizzare Testcontainer per simulare un database PostgreSQL, per il quale abbiamo definito un semplice Dockerfile

FROM postgres:14.3
ENV POSTGRES_USER=postgres
ENV POSTGRES_PASSWORD=postgres
ENV POSTGRES_DB=bddfun
COPY *.sql /docker-entrypoint-initdb.d/

Abbiamo predisposto un paio di script dove è presente la definizione delle tabelle che andremo ad utilizzare e per creare il nostro container basta effettuare la seguente build

docker build -t bddfun-database .

A questo punto dobbiamo decidere come andare ad aggiungere Testcontainer al nostro progetto. La parte principale è ovviamente aggiungere la dipendenza al pom.xml

<dependency>
	<groupId>org.testcontainers</groupId>
	<artifactId>postgresql</artifactId>
	<version>1.16.3</version>
	<scope>test</scope>
</dependency>

Ora rimane da decidere il modo in cui avviare Testcontainer durante i nostri test con JUnit e Cucumber. Una modalità abbastanza semplice è quella di andare semplicemente a definire il JDBC url che utilizzeremo per il nostro datasource in una maniera particolare

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

Grazie alle librerie che avremo importato e ad un pizzico di magia basata sul DriverManager JDBC, lanciando i casi test che usano le precedenti properties vedremo in console partire automaticamente anche l’istanza di PostgreSQL 14.3. Pur essendo drammaticamente semplice come metodo dovremmo poi gestire comunque il setup del DB magari mantenendo degli script a livello di progetto.

Esiste poi la modalità che ci permette di avviare le istanze del nostro database utilizzando le annotation @Testcontainers e @Container

package com.javastaff.bddfun.test.glue;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@Testcontainers
@DirtiesContext
public class BaseTest {

	@Container
	public static PostgreSQLContainer<?> postgresDB = new PostgreSQLContainer<>                
    		(DockerImageName.parse("bddfun-database").asCompatibleSubstituteFor("postgres"))
			.withDatabaseName("bddfun")												                        
            .withUsername("postgres")
            .withPassword("postgres");

	@DynamicPropertySource
	public static void properties(DynamicPropertyRegistry registry) {
		registry.add("spring.datasource.url",postgresDB::getJdbcUrl);
		registry.add("spring.datasource.username", postgresDB::getUsername);
		registry.add("spring.datasource.password", postgresDB::getPassword);
	}
}

L’annotation @Testcontainers ci permette di utilizzare @Container, che definisce a sua volta il database di cui abbiamo bisogno. Attraverso @DynamicPropertySource andremo poi a settare dinamicamente le informazioni del database per il nostro datasource. In questo approccio possiamo specificare la nostra immagine docker custom e il container viene ricreato ogni volta per ogni singola esecuzione di test. Il fatto di dedicare un container “pulito” ad ogni singolo test potrebbe portare alla lunga ad avere una build importante dal punto di vista dei tempi d’esecuzione.

Infine c’è approccio, scelto per questo articolo, che ci permette di definire manualmente lo start e stop del nostro database

package com.javastaff.bddfun.test.glue;

import javax.sql.DataSource;

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.ActiveProfiles;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.utility.DockerImageName;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

import io.cucumber.java.AfterAll;
import io.cucumber.java.BeforeAll;
import io.cucumber.spring.CucumberContextConfiguration;

@CucumberContextConfiguration
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@ActiveProfiles("bddtest")
public class SpringBootTestLoader {
	static PostgreSQLContainer postgresContainer;

	/**
	 * Initial database setup
	 */
	@BeforeAll
	public static void setup() {
		System.out.println("starting DB");
		DockerImageName myImage = DockerImageName.parse("bddfun-database")
                   .asCompatibleSubstituteFor("postgres");
		postgresContainer = new PostgreSQLContainer(myImage)
			       .withDatabaseName("bddfun")
			       .withUsername("postgres")
			       .withPassword("postgres");
		postgresContainer.start();
		System.out.println(postgresContainer.getJdbcUrl());
	}

	/**
	 * Datasource dynamic configuration
	 *
	 */
	@TestConfiguration
	static class PostgresTestConfiguration {
		@Bean
		DataSource dataSource() {
			HikariConfig hikariConfig = new HikariConfig();
			hikariConfig.setJdbcUrl(postgresContainer.getJdbcUrl());
			hikariConfig.setUsername(postgresContainer.getUsername());
			hikariConfig.setPassword(postgresContainer.getPassword());
			return new HikariDataSource(hikariConfig);
		}
	}

	/**
	 * Shutdown
	 */
	@AfterAll
	public static void tearDown() {
		System.out.println("closing DB connection");
		postgresContainer.stop();
	}
}

In questa modalità abbiamo già inserito l’annotation @CucumberContextConfiguration per indicare a Cucumber quale fosse il contesto dei nostri test. Utilizzando Spring Boot in questo esempio abbiamo poi aggiunto @SpringBootTest.

Attraverso questa configurazione avremo una singola istanza avviata per tutti i nostri test e lo start/stop sarà collegato ai metodi annotati con @BeforeAll e @AfterAll di Cucumber che vengono invocati appunto prima e dopo di tutti i test (simili a quelli di JUnit).

A questo punto possiamo avviare la nostra build ed se ci siamo ricordati di implementare gli endpoint del nostro blog vedremo che l’esecuzione andrà a buon fine ma soprattutto potremo consultare il report in html che è stato prodotto da Cucumber e che troveremo nella cartella target con il nome di cucumber-reports.html

Mockito

Il nostro sistema nella maggiorparte dei casi non si limita ad un’applicazione Spring Boot e un database, quindi per completare dei test veritieri potremmo anche capire come gestire altri sistemi. Sicuramente potremmo simularli tramite Testcontainer se avessimo a disposizione un’immagine docker oppure un’altra possibilità è quella di inserire una serie di mock nella parte di applicazione che dialoga con questi sistemi esterni.

Mockito è uno di quei framework che ben si sposano con questo approccio e che è possibile configurare semplicemente in questo setup insieme a Cucumber e Spring Boot. Se dovessimo quindi andare a definire un mock di un nostro servizio dovremmo farlo come al solito con l’annotation @MockBean di Mockito

@MockBean
private MyRemoteService myRemoteService;

Per agganciare questo mock ai nostri test con Cucumber dovremmo necessariamente riportare la precedente definizione all’interno della classe che ha l’annotation @CucumberContextConfiguration. In questo modo il mock verrà correttamente caricato nel contesto d’esecuzione di Cucumber. Per utilizzare poi il mock all’interno del nostro caso di test potremo semplicemente richiamarlo attraverso @Autowired

@Autowired
private MyRemoteService myRemoteService

...
...

@Given("something")
public void setupSomething() {
	when(myRemoteService.getSomething(any())).thenReturn("Hello from mock");
}

Codice d’esempio

Tutto il progetto che è stato illustrato all’interno dell’articolo è disponibile al seguente repository Github

Federico Paparoni

Looking for a right "about me"...

Una risposta

  1. Dicembre 2, 2022

    […] e tool che ci permettono di gestire questa tipologia di test e BDD con la sintassi Gherkin, come visto in altri casi, è molto utile per modellare cosa viene eseguito nelle diverse funzionalità del nostro […]

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.