Il testing del software è fondamentale per garantire che le vostre applicazioni funzionino come previsto, soprattutto quando introducete delle modifiche. Individuare e correggere gli errori nelle prime fasi dello sviluppo è fondamentale per mantenere un codice resistente e di alta qualità.

Tra i tanti strumenti e framework disponibili per il testing di JavaScript, Jest è uno dei più popolari. Prodotto da Meta, Jest offre ampie funzionalità di testing per le applicazioni JavaScript e per quelle realizzate con framework JavaScript.

Vediamo insieme il framework Jest, le sue caratteristiche e il modo migliore per integrarlo nel vostro workflow di sviluppo.

Cos’è Jest?

Jest è un framework flessibile e semplice da usare. Oltre alle sue funzioni principali di test JavaScript, offre configurazioni e plugin per supportare il test di applicazioni basate su Babel, webpack, Vite, Parcel o TypeScript.

Jest è stato largamente adottato da chi lavora nello sviluppo e vanta una serie di plugin costruiti e gestiti dalla comunità. Si distingue per la sua facilità d’uso: i test JavaScript non richiedono configurazioni o plugin aggiuntivi. Ma potete anche eseguire test più avanzati, come quelli sui framework JavaScript, utilizzando alcune opzioni di configurazione aggiuntive.

Come configurare Jest per il vostro progetto JavaScript

Vediamo come configurare Jest in un progetto JavaScript esistente.

Prerequisiti

Per seguire questo tutorial, assicuratevi di avere i seguenti requisiti:

Installare il pacchetto Jest

  1. Se non avete già un progetto da seguire in questo tutorial, usate questo repo come punto di partenza.

Il branch starter-files vi fornisce una base per costruire l’applicazione mentre seguite il tutorial. Fate riferimento al main per visualizzare il codice di questo tutorial e fare un controllo incrociato del vostro codice.

  1. Per installare Jest con npm, andate nella directory del progetto nel vostro terminale ed eseguite il seguente comando:
npm install --save-dev jest

L’opzione --save-dev indica a npm di installare il pacchetto sotto devDependencies, che contiene le dipendenze necessarie per lo sviluppo.

Configurare Jest

Anche se Jest funziona generalmente senza alcuna configurazione aggiuntiva, ci sono due modi per espandere la sua potenza: dal file package.json e tramite un file di configurazione di Jest.

Configurare Jest in package.json

Nel file package.json, aggiungete un oggetto chiamato jest con le proprietà mostrate di seguito:

{
  …
  "jest": {
	"displayName": "Ecommerce",
	"globals": {
  	"PROJECT_NAME": "Ecommerce TD"
	},
	"bail": 20,
	"verbose": true
  },
}

Durante il test, Jest cerca questo oggetto e applica queste configurazioni. Potete visualizzare altre opzioni nella pagina delle configurazioni di Jest, ma le proprietà di questo oggetto includono:

  • displayName: Jest aggiunge il valore di questa proprietà come etichetta ai risultati del test.
  • globals: contiene il valore di un oggetto per definire le variabili globali disponibili negli ambienti di test.
  • bail: per impostazione predefinita, Jest esegue tutti i test e visualizza gli errori nei risultati. bail indica a Jest di interrompere l’esecuzione dopo un determinato numero di test non riusciti.
  • verbose: se impostato su true, mostra i report dei singoli test durante la loro esecuzione.

Configurare Jest in un file di configurazione

Potete anche configurare Jest in un file jest.config.js. Jest supporta anche le estensioni .ts, .mjs, .cjs e .json. Quando esegue i test, Jest cerca questi file e applica le impostazioni del file che trova.

Per esempio, prendiamo questo file jest.config.js:

const config = {
  displayName: "Ecommerce",
  globals: {
	"PROJECT_NAME": "Ecommerce TD"
  },
  bail: 20,
  verbose: true
}

module.exports = config;

Il codice esporta un oggetto di configurazione Jest con le stesse proprietà dell’esempio precedente.

Potete anche usare un file personalizzato che contenga un oggetto di configurazione serializzabile JSON e passare il percorso del file all’opzione --config durante l’esecuzione dei test.

Creare un file di test di base

Dopo aver configurato Jest, create i vostri file di test. Jest esamina i file di test del vostro progetto, li esegue e fornisce i risultati. I file di test di solito seguono un formato simile a [nome].test.js o [nome]-test.js. Questo schema facilita l’identificazione dei file di test sia da parte di Jest che del vostro team.

Prendiamo in esame un file string-format.js con il seguente codice:

function truncate(
  str,
  count,
  withEllipsis = true
) {
  if (str.length < = count)
	return str

  const substring = str.substr(0, count)

  if (!withEllipsis)
	return substring

  return substring + '...'
}

module.exports = { truncate }

La funzione truncate() tronca le stringhe a una determinata lunghezza con l’opzione di aggiungere un’ellissi.

Scrivere il test

  1. Create un file di prova chiamato string-format.test.js.
  1. Per mantenere l’organizzazione dei vostri file, posizionate string-format.test.js nella stessa directory in cui si trova il file string-format.js o in una directory di test specifica. Indipendentemente dalla posizione del file di test all’interno del progetto, Jest lo trova e lo esegue. Con Jest potete testare le vostre applicazioni in vari scenari.
  2. Scrivete un test di base in string-format.test.js come segue:
const { truncate } = require('./string-format')

test('truncates a string correctly', () = > {
  expect(truncate("I am going home", 6)).toBe('I am g...')
})

Il caso di test ha la descrizione truncates a string correctly. Questo codice usa la funzione expect fornita da Jest, che verifica se un valore corrisponde al risultato atteso.

Il codice passa truncate("I am going home", 6) come argomento a expect. Questo codice testa il valore restituito dalla chiamata a truncate con gli argomenti "I am going home" e 6. La chiamata a expect restituisce un oggetto expectation, che offre accesso alle corrispondenze di Jest.

Contiene anche il matcher toBe, che ha "I am g…" come argomento. Il matcher toBe verifica l’uguaglianza tra i valori attesi e quelli reali.

Eseguire il test

Per eseguire i vostri test, definite il comando jest.

  1. Nel file package.json del vostro progetto, aggiungete questo script test:
"scripts": {
  "test": "jest"
}
  1. Ora eseguite npm run test, npm test o npm t nel vostro terminale. Esegue Jest per il progetto.

Quando eseguite i test, il risultato è questo:

Il risultato del test Jest mostra il superamento del test "truncates a string correctly" in string-format.test.js.
Risultato del test Jest per string-format.test.js.

I risultati mostrano una suite di test (il file string-format.test.js), un test eseguito con successo ("truncates a string correctly") e il displayName (Ecommerce) che avete definito nella configurazione.

  1. In string-format.js, se aggiungete un punto in più per interrompere il codice ed eseguire il test, questo fallisce:
Il risultato del test Jest è fallito per il test "truncates a string correctly" in string-format.test.js. La stringa attesa dal test è "i am g...", ma la stringa ricevuta è "i am g....".
Risultato fallito del test Jest per una funzione truncate interrotta”.

Questo risultato suggerisce che la funzione truncate è stata interrotta o che sono stati fatti degli aggiornamenti che richiedono l’aggiornamento dei test.

Come scrivere i test con Jest

Sintassi dei test Jest

La sintassi proprietaria di Jest è semplice da usare. Jest espone al vostro progetto metodi e oggetti globali per la scrittura dei test. Alcuni dei suoi termini fondamentali sono describe, test, expect e matcher.

  • describe: questa funzione raggruppa i test correlati in un file.
  • test: questa funzione esegue il test. È un alias di it. Contiene le asserzioni per i valori che volete testare.
  • expect: questa funzione dichiara le asserzioni per i vari valori. Fornisce l’accesso ai matcher per varie forme di asserzioni.
  • Matcher: vi permettono di asserire un valore in vari modi. Potete asserire l’uguaglianza dei valori, l’uguaglianza booleana e l’uguaglianza contestuale (per esempio se un array contiene il valore).

Per usarli, considerate il seguente esempio:

  1. Sostituite il test nel file string-format.test.js con il seguente codice:
describe("all string formats work as expected", () = > {
  test("truncates a string correctly", () = > {
	expect(
  	truncate("I am going home", 6)
	).toBe("I am g...")
  })
})
  1. Eseguite il codice.

Il risultato è il seguente:

Il risultato del test Jest mostra il superamento del test "truncates a string correctly" in string-format.test.js. Il risultato del test mostra anche il testo "tutti i formati di stringa funzionano come previsto" dalla funzione describe.
Risultato del test Jest che mostra l’etichetta di descrizione.

La schermata mostra che l’etichetta della funzione describe crea un blocco. Sebbene describe sia facoltativo, è utile raggruppare i test in un file con un contesto più ampio.

Organizzare i test in suite di test

In Jest, un caso di test è composto dalla funzione test, dalla funzione expect e da un matcher. Un insieme di casi di test correlati è una suite di test. Nell’esempio precedente, string-format.test.js è una suite di test che comprende un caso di test per verificare il file string-format.js.

Supponiamo di avere altri file nel progetto, come file-operations.js, api-logger.js e number-format.js. Potete creare delle suite di test per questi file, come file-operations.test.js, api-logger.test.js e number-format.test.js.

Scrivere semplici asserzioni con Jest Matchers

Abbiamo esplorato un esempio di utilizzo del matcher toBe. Le asserzioni con altri matcher Jest includono:

  • toEqual: per verificare l’uguaglianza “profonda” nelle istanze degli oggetti.
  • toBeTruthy: per verificare se un valore è vero in un contesto booleano.
  • toBeFalsy: per verificare se un valore è falso in un contesto booleano.
  • toContain: per verificare che un array contenga un valore.
  • toThrow: per verificare che una funzione invocata lanci un errore.
  • stringContaining: per verificare che una stringa contenga una sottostringa.

Vediamo alcuni esempi di utilizzo di questi matcher.

Per esempio, potreste aspettarvi che una funzione o un codice restituisca un oggetto con proprietà e valori specifici.

  1. Usate lo snippet di codice qui sotto per testare questa funzionalità. In questo caso, volete verificare che l’oggetto restituito sia uguale all’oggetto previsto.
expect({
  name: "Joe",
  age: 40
}).toBe({
  name: "Joe",
  age: 40
})

Questo esempio usa toBe. Il test fallisce perché questo matcher non controlla l’uguaglianza profonda: controlla il valore, non tutte le proprietà.

  1. Usate il matcher toEqual per verificare l’uguaglianza profonda:
expect({
  name: "Joe",
  age: 40
}).toEqual({
  name: "Joe",
  age: 40
})

Questo test viene superato perché entrambi gli oggetti sono “profondamente uguali”, ovvero tutte le loro proprietà sono uguali.

  1. Provate un altro esempio di matcher che verifica se l’array definito contiene un elemento specifico.
expect(["orange", "pear", "apple"]).toContain("mango")

Questo test fallisce perché toContain afferma che l’array ["orange", "pear", "apple"] contiene il valore atteso "mango", ma l’array non lo contiene.

  1. Usate le variabili per lo stesso test del codice sottostante:
const fruits = ["orange", "pear", "apple"];
const expectedFruit = "mango";

expect(fruits).toContain(expectedFruit)

Testare il codice asincrono

Finora abbiamo testato il codice sincrono, cioè espressioni che restituiscono un valore prima che il codice esegua la riga successiva. Potete usare Jest anche per il codice asincrono con async, await o Promises.

Per esempio, il file apis.js contiene una funzione per effettuare una richiesta API:

function getTodos() {
  return fetch('https://jsonplaceholder.typicode.com/todos/1')
}

La funzione getTodos invia una richiesta GET a https://jsonplaceholder.typicode.com/todos/1.

  1. Create un file chiamato apis.test.js con il seguente codice per testare la finta API:
const { getTodos } = require('./apis')

test("gets a todo object with the right properties", () = > {
  return getTodos()
	.then((response) = > {
  	return response.json()
	})
	.then((data) = > {
  	expect(data).toHaveProperty('userId')
  	expect(data).toHaveProperty('id')
  	expect(data).toHaveProperty('title')
  	expect(data).toHaveProperty('completed')
  	expect(data).toHaveProperty('description')
	})
})

Questo caso di test invoca la funzione getTodos che recupera un oggetto todo. Quando risolve la Promise, usa il metodo .then per ottenere il valore risolto.

In questo valore, il codice restituisce response.json(), che è un’altra Promise che converte la risposta in formato JSON. Un altro metodo .then ottiene l’oggetto JSON contenente expect e i matcher. Il codice afferma che l’oggetto JSON include cinque proprietà: userId, id, title, completed e description.

  1. Eseguire i test:

Il risultato del test Jest mostra un fallimento per il test
Il risultato del test Jest mostra un test fallito per il codice asincrono.

Come mostra lo screenshot, il test per getTodos() fallisce. Si aspetta la proprietà description, ma l’API non la restituisce. Grazie a queste informazioni, potete chiedere al team di gestione delle API della vostra azienda di includere questa proprietà se l’applicazione ne ha bisogno o di aggiornare i test per soddisfare la risposta dell’API.

  1. Rimuovete l’asserzione per la proprietà description e rieseguite i test:

Il risultato del test Jest mostra che è stato superato il test "truncates a string correctly" in string-format.test.js e "ottiene un oggetto todo con le giuste proprietà" in apis.test.js.
Il risultato del test Jest mostra un test superato per il codice asincrono.

Lo screenshot mostra che tutto ha superato il test.

  1. Ora provate a usare async/await invece della tradizionale gestione di Promise:
test("gets a todo object with the right properties", async () = > {
  const response = await getTodos()
  const data = await response.json()

  expect(data).toHaveProperty("userId")
  expect(data).toHaveProperty("id")
  expect(data).toHaveProperty("title")
  expect(data).toHaveProperty("completed")
})

La parola chiave async si trova ora prima della funzione. Il codice usa await prima di getTodos() e await prima di response.json().

Caratteristiche avanzate di Jest

Funzioni e moduli mock

Quando scrivete i test, potreste voler testare un’espressione con dipendenze esterne. In alcuni casi, soprattutto nei test unitari, i test unitari devono essere isolati dagli effetti esterni. In questo caso, potete fare il mock delle vostre funzioni o dei vostri moduli con Jest per controllare meglio i vostri test.

  1. Per esempio, consideriamo un file functions.js che contiene il seguente codice:
function multipleCalls(count, callback) {
  if (count < 0) return;

  for (let counter = 1; counter <= count; counter++) {
	callback()
  }
}

La funzione multipleCalls viene eseguita in base al valore di count. Essa dipende dalla funzione di callback, la dipendenza esterna. Il suo scopo è sapere se multipleCalls esegue correttamente la dipendenza esterna.

  1. Per simulare la dipendenza esterna e monitorare lo stato della dipendenza nel vostro file di test, functions.test.js, usate questo codice:
const { multipleCalls } = require('./functions')

test("functions are called multiple times correctly", () => {
  const mockFunction = jest.fn()

  multipleCalls(5, mockFunction)

  expect(
	mockFunction.mock.calls.length
  ).toBe(5)
})

Qui, il metodo fn dell’oggetto jest crea una funzione mock. Poi, il codice esegue multipleCalls passando 5 e la funzione mock come argomenti. Quindi, afferma che mockFunction viene chiamato cinque volte. La proprietà mock contiene informazioni su come il codice chiama la funzione e sui valori restituiti.

  1. Quando si esegue il test, questo è il risultato atteso:

Il risultato del test Jest mostra che i tre test sono stati superati: "truncates a string correctly", "functions are called multiple times correctly" e "gets a todo object with the right properties"
Risultato del test Jest con una funzione mock.

Come dimostrato, il codice chiama il sito mockFunction cinque volte.

Nel codice, la funzione mock imita una dipendenza esterna. Non importa quale sia la dipendenza esterna quando l’applicazione usa multipleCalls in produzione. Al vostro test unitario non interessa come funziona la dipendenza esterna. Verifica solo che multipleCalls funzioni come previsto.

  1. Per simulare i moduli, usate il metodo mock e passate un percorso di file, che è il modulo:
const {
  truncate,
} = require("./string-format")

jest.mock("./string-format.js")

Questo codice imita tutte le funzioni che string-format.js esporta e tiene traccia della frequenza con cui le chiama. La funzione truncate del modulo diventa una funzione mock, che fa perdere alla funzione la sua logica originale. Potete scoprire quante volte truncate viene eseguito nei vostri test nella proprietà truncate.mock.calls.length.

Se vedete un errore o il vostro codice non funziona, confrontatelo con l’implementazione completa.

Testare i componenti React con Jest e la libreria di test React

Se non avete già un progetto da seguire in questo tutorial, potete usare questo progetto di esempio React come punto di partenza. Il branch starter-files vi aiuta a iniziare a comporre il codice mentre seguite il tutorial. Usate il main come riferimento per fare un controllo incrociato tra il vostro codice e il codice completo di questo tutorial.

Potete usare Jest per testare framework JavaScript come React. Quando create progetti React con Create React App, questi supportano la React Testing Library e Jest. Se create un progetto React senza Create React App, installate Jest per testare React con Babel e la libreria di testing React. Se clonate il ramo starter-app, non dovrete installare le dipendenze o applicare le configurazioni.

  1. Se state usando il progetto di esempio, usate questo comando per installare le dipendenze necessarie:
npm install --save-dev babel-jest @babel/preset-env @babel/preset-react react-testing-library

Potete anche usare Enzyme al posto di React Testing Library.

  1. Aggiornate le configurazioni di Babel in babel.config.js o create questo file se non esiste:
module.exports = {
  presets: [
	'@babel/preset-env',
  	['@babel/preset-react', {runtime: 'automatic'}],
  ],
};
  1. Considerate il file src/SubmitButton.js che ha il seguente codice:
import React, { useState } from 'react'

export default function SubmitButton(props) {
  const {id, label, onSubmit} = props
  const [isLoading, setisLoading] = useState(false)

  const submit = () => {
	setisLoading(true)
	onSubmit()
  }

  return 

Questo componente SubmitButton riceve tre prop:

  • id: l’identificatore del pulsante.
  • label: il testo da rendere nel pulsante.
  • onSubmit: quale funzione attivare quando qualcuno fa clic sul pulsante.

Il codice assegna il prop id all’attributo data-testid, che identifica un elemento da testare.

Il componente tiene traccia anche dello stato isLoading e lo aggiorna a true quando qualcuno fa clic sul pulsante.

  1. Create il test per questo componente. Inserite il seguente codice nel file SubmitButton.test.js:
import {fireEvent, render, screen} from "@testing-library/react"
import "@testing-library/jest-dom"
import SubmitButton from "./SubmitButton"

test("SubmitButton becomes disabled after click", () => {
  const submitMock = jest.fn()

  render(
	<SubmitButton
  	id="submit-details"
  	label="Submit"
  	onSubmit={submitMock}
	/ >
  )

  expect(screen.getByTestId("submit-details")).not.toBeDisabled()

  fireEvent.submit(screen.getByTestId("submit-details"))

  expect(screen.getByTestId("submit-details")).toBeDisabled()
})

Il codice precedente esegue il rendering del componente SubmitButton e usa il metodo di interrogazione screen.getByTestId per ottenere il nodo DOM in base all’attributo data-testid.

Il primo expect è getByTestId("submit-details") e usa il modificatore not e il matcher toBeDisabled (esposto da react-testing-library) per affermare che il pulsante non è disabilitato. Usate il modificatore not con ogni matcher per affermare il contrario del matcher.

Quindi il codice lancia l’evento submit sul componente e verifica che il pulsante sia disabilitato. Potete trovare altri matcher personalizzati nella documentazione della libreria di test.

  1. Ora eseguite i test. Se avete clonato il branch starter-files, assicuratevi di aver installato tutte le dipendenze del progetto eseguendo npm install prima di iniziare i test.

Il truncates a string correctly", "functions are called multiple times correctly", "gets a todo object with the right properties" e "SubmitButton becomes disabled after click"
Il risultato del test Jest mostra che il test di un componente react è stato superato.

Eseguire i report di copertura del codice

Jest offre anche dei report sulla copertura del codice per mostrare la parte del progetto che state testando.

  1. Passate l’opzione --coverage a Jest. Nello script Jest in package.json (nel progetto JavaScript), aggiornate il comando Jest con questa opzione di copertura:
"scripts": {
  "test": "jest --coverage"
}
  1. Eseguite npm run test per testare il vostro codice. Otterrete un report come il seguente:

Il risultato del test Jest mostra la copertura dei quattro test. Il risultato mostra che "SubmitButton.js" ha una copertura del 100%, mentre string-format.js ha una copertura del 76,92%.
Report di copertura Jest di successo per ogni suite di test.

Questo report mostra che Jest ha testato il 100% delle funzioni in SubmitButton.js e string-format.js. Indica inoltre che Jest non ha testato nessuna istruzione e nessuna riga di string-format.js. La copertura dei test mostra che le linee scoperte in string-format.js sono 7 e 12.Alla riga 7, return str nella funzione truncate non viene eseguito perché la condizione if (str.length <= count) restituisce false.

Alla riga 12, sempre nella funzione truncate, return substring non viene eseguito perché la condizione if (!withEllipsis) ritorna false.

Integrare Jest con il vostro workflow di sviluppo

Vediamo come integrare questi test per migliorare il vostro workflow di sviluppo.

Eseguire i test in modalità Watch

Invece di eseguire manualmente i test, potete eseguirli automaticamente quando modificate il codice usando la modalità di osservazione.

  1. Per abilitare la modalità di osservazione, aggiornate il vostro script di comando Jest in package.json (nel progetto JavaScript) aggiungendo l’opzione --watchAll:
"scripts": {
  "test": "jest --coverage --watchAll"
}
  1. Eseguite npm run test. Attivate Jest in modalità watch:

Jest in modalità watch, che mostra quattro test superati e anche un elenco di comandi da usare in modalità watch. Mostra il comando f, per eseguire solo i test falliti; o, per eseguire solo i test relativi ai file modificati; p, per filtrare in base a un pattern regex del nome del file; t, per filtrare in base a un pattern regex del nome del test; q per abbandonare la modalità watch; Enter, per attivare l'esecuzione di un test.
Esecuzione di Jest in modalità watch.

I test vengono eseguiti ogni volta che modificate il progetto. Questo approccio favorisce un feedback continuo durante la creazione dell’applicazione.

Impostare gli hook pre-commit

Negli ambienti Git, gli hook eseguono gli script ogni volta che si verifica un particolare evento (come un pull, un push o un commit). Gli hook di pre-commit definiscono quali script vengono eseguiti per l’evento di pre-commit (che il codice attiva prima di effettuare un commit).

Il commit va a buon fine solo se lo script non lancia un errore.

L’esecuzione di Jest prima del pre-commit assicura che nessuno dei vostri test fallisca prima del commit.

Potete usare diverse librerie per impostare gli hook di git nel vostro progetto, come per esempio ghooks.

  1. Installate ghooks sotto devDependencies:
npm install ghooks --save-dev
  1. Aggiungete un oggetto configs nel livello superiore del vostro file package.json (nel progetto JavaScript).
  2. Aggiungete un oggetto ghooks sotto configs.
  1. Aggiungete una proprietà con una chiave di pre-commit e un valore di jest.
{
  …
  "config": {
	"ghooks": {
  	"pre-commit": "jest"
	}
  },
}
  1. Eseguite il commit del codice. Il codice attiva l’hook pre-commit, che esegue Jest:

Esecuzione di Jest durante una fase di pre-commit. Quando si esegue un commit usando git commit -m sul terminale, Jest viene eseguito e vengono visualizzati i risultati dei test.
Eseguire Jest durante il pre-commit usando i ghook.

Riepilogo

Ora sapete come integrare Jest nel vostro flusso di sviluppo per eseguirlo automaticamente ogni volta che apportate una modifica. Questo approccio fornisce un feedback continuo che vi permette di correggere rapidamente eventuali problemi del codice prima di rilasciare le modifiche in produzione.

Ospitando la vostra applicazione con Kinsta, potrete beneficiare di un’infrastruttura veloce e sicura, e distribuire i vostri progetti su un’infrastruttura costruita sulla rete Premium Tier di Google Cloud Platform e su macchine C2. Potrete scegliere tra 37 data center e un CDN abilitato HTTP/3 con 260+ PoPs.

Restate al sicuro con la tecnologia dei container isolati, due firewall potenti e una protezione DDoS avanzata alimentata da Cloudflare. Inoltre, potete integrare le app o automatizzare i flussi di lavoro con l’API di Kinsta.

Configurate Jest e consultate le risorse di Kinsta oggi stesso per migliorare le vostre applicazioni JavaScript.

Marcia Ramos Kinsta

I'm the Editorial Team Lead at Kinsta. I'm a open source enthusiast and I love coding. With more than 7 years of technical writing and editing for the tech industry, I love collaborating with people to create clear and concise pieces of content and improve workflows.