Vai al contenuto principale
{ "message": "Versione: Prossima", "description": "" }

Test con Snapshot

Traduzione Beta Non Ufficiale

Questa pagina è stata tradotta da PageTurner AI (beta). Non ufficialmente approvata dal progetto. Hai trovato un errore? Segnala problema →

I test snapshot sono uno strumento molto utile quando vuoi assicurarti che la tua UI non cambi inaspettatamente.

Un tipico test snapshot esegue il rendering di un componente UI, acquisisce uno snapshot, quindi lo confronta con un file snapshot di riferimento memorizzato insieme al test. Il test fallirà se i due snapshot non corrispondono: il cambiamento potrebbe essere inatteso, oppure lo snapshot di riferimento deve essere aggiornato alla nuova versione del componente UI.

Test Snapshot con Jest

Un approccio simile può essere adottato per testare i componenti React. Invece di eseguire il rendering dell'interfaccia grafica, che richiederebbe la costruzione dell'intera app, puoi utilizzare un renderer di test per generare rapidamente un valore serializzabile per il tuo componente React. Considera questo test di esempio per un componente Link:

import {render} from '@testing-library/react';
import Link from '../Link';

it('renders correctly', () => {
const {container} = render(
<Link page="http://www.facebook.com">Facebook</Link>,
);
expect(container.firstChild).toMatchSnapshot();
});

La prima volta che questo test viene eseguito, Jest crea un file snapshot simile a questo:

exports[`renders correctly 1`] = `
<a
aria-label="normal
class="normal"
href="http://www.facebook.com"
>
Facebook
</a>
`;

L'artefatto dello snapshot dovrebbe essere commitato insieme alle modifiche al codice e revisionato come parte del processo di code review. Jest utilizza pretty-format per rendere gli snapshot leggibili durante la code review. Nelle esecuzioni successive dei test, Jest confronterà l'output renderizzato con lo snapshot precedente. Se corrispondono, il test passerà. Se non corrispondono, significa che il test runner ha trovato un bug nel tuo codice (in questo caso nel componente <Link>) che dovrebbe essere corretto, oppure l'implementazione è cambiata e lo snapshot deve essere aggiornato.

nota

Lo snapshot è limitato esclusivamente ai dati che renderizzi – nel nostro esempio il componente <Link> con la prop page passata. Ciò implica che anche se in altri file mancano props (ad esempio in App.js) per il componente <Link>, il test passerà comunque perché il test non conosce l'utilizzo del componente <Link> ed è limitato solo al file Link.js. Inoltre, eseguire il rendering dello stesso componente con props diverse in altri test snapshot non influenzerà il primo, poiché i test non sono consapevoli l'uno dell'altro.

info

Ulteriori informazioni sul funzionamento dei test snapshot e sul motivo per cui li abbiamo creati sono disponibili nel post di blog di rilascio. Ti consigliamo di leggere questo articolo per capire quando è opportuno utilizzare i test snapshot. Ti suggeriamo anche di guardare questo video egghead sui Test Snapshot con Jest.

Aggiornare gli Snapshot

È semplice individuare quando un test snapshot fallisce dopo l'introduzione di un bug. In quel caso, procedi a correggere il problema e assicurati che i test snapshot passino nuovamente. Ora parliamo del caso in cui un test snapshot fallisce a causa di una modifica intenzionale dell'implementazione.

Una situazione del genere può verificarsi se modifichiamo intenzionalmente l'indirizzo a cui punta il componente Link nel nostro esempio.

// Updated test case with a Link to a different address
it('renders correctly', () => {
const {container} = render(
<Link page="http://www.instagram.com">Instagram</Link>,
);
expect(container.firstChild).toMatchSnapshot();
});

In tal caso, Jest produrrà questo output:

Poiché abbiamo appena aggiornato il nostro componente per puntare a un indirizzo diverso, è ragionevole aspettarsi cambiamenti nello snapshot di questo componente. Il nostro caso di test snapshot fallisce perché lo snapshot del componente aggiornato non corrisponde più all'artefatto snapshot per questo caso di test.

Per risolvere questo problema, dovremo aggiornare i nostri artefatti snapshot. Puoi eseguire Jest con un flag che gli indica di rigenerare gli snapshot:

jest --updateSnapshot

Procedi ad accettare le modifiche eseguendo il comando sopra indicato. Puoi anche usare l'equivalente flag a carattere singolo -u per rigenerare gli snapshot se preferisci. Questo rigenererà gli artefatti snapshot per tutti i test snapshot falliti. Se avessimo altri test snapshot falliti a causa di un bug non intenzionale, dovremmo correggere il bug prima di rigenerare gli snapshot per evitare di registrare snapshot del comportamento difettoso.

Se vuoi limitare quali casi di test snapshot vengono rigenerati, puoi passare il flag aggiuntivo --testNamePattern per registrare nuovamente gli snapshot solo per i test che corrispondono al pattern.

Puoi provare questa funzionalità clonando l'esempio snapshot, modificando il componente Link ed eseguendo Jest.

Modalità Snapshot Interattiva

Gli snapshot falliti possono anche essere aggiornati interattivamente in modalità watch:

Una volta entrato nella Modalità Snapshot Interattiva, Jest ti guiderà attraverso gli snapshot falliti un test alla volta e ti darà l'opportunità di rivedere l'output non riuscito.

Da qui puoi scegliere di aggiornare quello snapshot o passare al successivo:

Una volta terminato, Jest ti fornirà un riepilogo prima di tornare alla modalità watch:

Snapshot Inline

Gli snapshot inline si comportano identicamente agli snapshot esterni (file .snap), tranne per il fatto che i valori degli snapshot vengono scritti automaticamente nel codice sorgente. Questo significa che puoi beneficiare degli snapshot generati automaticamente senza dover passare a un file esterno per verificare che il valore corretto sia stato scritto.

Esempio:

Per prima cosa, scrivi un test chiamando .toMatchInlineSnapshot() senza argomenti:

it('renders correctly', () => {
const {container} = render(
<Link page="https://example.com">Example Site</Link>,
);
expect(container.firstChild).toMatchInlineSnapshot();
});

La prossima volta che esegui Jest, tree verrà valutato e uno snapshot verrà scritto come argomento di toMatchInlineSnapshot:

it('renders correctly', () => {
const {container} = render(
<Link page="https://example.com">Example Site</Link>,
);
expect(container.firstChild).toMatchInlineSnapshot(`
<a
aria-label="normal"
className="normal"
href="https://example.com"
>
Example Site
</a>
`);
});

È tutto qui! Puoi persino aggiornare gli snapshot con --updateSnapshot o usando il tasto u in modalità --watch.

Per impostazione predefinita, Jest gestisce la scrittura degli snapshot nel tuo codice sorgente. Tuttavia, se stai usando prettier nel tuo progetto, Jest lo rileverà e delegherà il lavoro a prettier (rispettando anche la tua configurazione).

Property Matchers

Spesso ci sono campi nell'oggetto che vuoi sottoporre a snapshot che vengono generati (come ID e Date). Se provi a fare lo snapshot di questi oggetti, forzeranno il fallimento dello snapshot ad ogni esecuzione:

it('will fail every time', () => {
const user = {
createdAt: new Date(),
id: Math.floor(Math.random() * 20),
name: 'LeBron James',
};

expect(user).toMatchSnapshot();
});

// Snapshot
exports[`will fail every time 1`] = `
{
"createdAt": 2018-05-19T23:36:09.816Z,
"id": 3,
"name": "LeBron James",
}
`;

Per questi casi, Jest permette di fornire un matcher asimmetrico per qualsiasi proprietà. Questi matcher vengono controllati prima che lo snapshot venga scritto o testato, e poi salvati nel file snapshot invece del valore ricevuto:

it('will check the matchers and pass', () => {
const user = {
createdAt: new Date(),
id: Math.floor(Math.random() * 20),
name: 'LeBron James',
};

expect(user).toMatchSnapshot({
createdAt: expect.any(Date),
id: expect.any(Number),
});
});

// Snapshot
exports[`will check the matchers and pass 1`] = `
{
"createdAt": Any<Date>,
"id": Any<Number>,
"name": "LeBron James",
}
`;

Qualsiasi valore fornito che non sia un matcher verrà controllato esattamente e salvato nello snapshot:

it('will check the values and pass', () => {
const user = {
createdAt: new Date(),
name: 'Bond... James Bond',
};

expect(user).toMatchSnapshot({
createdAt: expect.any(Date),
name: 'Bond... James Bond',
});
});

// Snapshot
exports[`will check the values and pass 1`] = `
{
"createdAt": Any<Date>,
"name": 'Bond... James Bond',
}
`;
suggerimento

Se il caso riguarda una stringa e non un oggetto, devi sostituire manualmente la parte casuale di quella stringa prima di testare lo snapshot.
Puoi usare ad esempio replace() e espressioni regolari.

const randomNumber = Math.round(Math.random() * 100);
const stringWithRandomData = `<div id="${randomNumber}">Lorem ipsum</div>`;
const stringWithConstantData = stringWithRandomData.replace(/id="\d+"/, 123);
expect(stringWithConstantData).toMatchSnapshot();

Altri modi per farlo includono l'uso di uno snapshot serializer o il mocking della libreria responsabile della generazione della parte casuale del codice che stai snapshotando.

Best Practices

Gli snapshot sono uno strumento eccezionale per identificare modifiche impreviste nell'interfaccia della tua applicazione – che si tratti di risposte API, UI, log o messaggi d'errore. Come per qualsiasi strategia di testing, esistono best practice da conoscere e linee guida da seguire per un utilizzo efficace.

1. Tratta gli snapshot come codice

Includi gli snapshot nei commit e revisionali come parte del tuo normale processo di code review. Questo significa considerarli alla stregua di qualsiasi altro test o codice nel tuo progetto.

Garantisci leggibilità mantenendo gli snapshot focalizzati, concisi e utilizzando strumenti che impongano queste convenzioni stilistiche.

Come menzionato precedentemente, Jest usa pretty-format per rendere gli snapshot leggibili, ma può essere utile introdurre strumenti aggiuntivi come eslint-plugin-jest con l'opzione no-large-snapshots o snapshot-diff con la sua funzione di confronto componenti, per favorire commit di asserzioni brevi e mirate.

L'obiettivo è semplificare la revisione degli snapshot nelle pull request e contrastare l'abitudine di rigenerarli automaticamente quando i test falliscono, invece di analizzarne le cause profonde.

2. I test devono essere deterministici

I tuoi test devono essere deterministici. Eseguire ripetutamente gli stessi test su un componente invariato deve produrre sempre gli stessi risultati. Spetta a te garantire che gli snapshot generati non includano dati specifici di piattaforma o non deterministici.

Ad esempio, se disponi di un componente Clock che utilizza Date.now(), lo snapshot generato sarà diverso a ogni esecuzione del test. In questo caso possiamo simulare il metodo Date.now() per restituire un valore coerente:

Date.now = jest.fn(() => 1_482_363_367_071);

Ora, a ogni esecuzione del test snapshot, Date.now() restituirà costantemente 1482363367071. Ciò produrrà uno snapshot identico per questo componente indipendentemente dal momento di esecuzione.

3. Usa nomi descrittivi per gli snapshot

Scegli sempre nomi descrittivi per test e/o snapshot che ne indichino chiaramente il contenuto atteso. Questo facilita la verifica durante le review e permette a chiunque di valutare se uno snapshot obsoleto rappresenti comunque il comportamento corretto prima di aggiornarlo.

Confronta ad esempio:

exports[`<UserName /> should handle some test case`] = `null`;

exports[`<UserName /> should handle some other test case`] = `
<div>
Alan Turing
</div>
`;

A:

exports[`<UserName /> should render null`] = `null`;

exports[`<UserName /> should render Alan Turing`] = `
<div>
Alan Turing
</div>
`;

Poiché quest'ultimo descrive esattamente ciò che ci si aspetta nell'output, è più chiaro vedere quando è sbagliato:

exports[`<UserName /> should render null`] = `
<div>
Alan Turing
</div>
`;

exports[`<UserName /> should render Alan Turing`] = `null`;

Domande Frequenti

Gli snapshot vengono generati automaticamente sui sistemi CI?

No, a partire da Jest 20 gli snapshot non vengono generati automaticamente nelle esecuzioni CI senza passare esplicitamente --updateSnapshot. Tutti gli snapshot devono far parte del codice eseguito in CI e, poiché i nuovi snapshot passano automaticamente, non dovrebbero superare un test run in ambiente CI. Si raccomanda di includere sempre gli snapshot nei commit e mantenerli nel version control.

I file snapshot vanno inclusi nei commit?

Sì, tutti i file snapshot devono essere commitati insieme ai moduli e ai test che coprono. Vanno considerati parte integrante del test, analogamente a qualsiasi altra asserzione in Jest. Gli snapshot rappresentano infatti lo stato dei moduli sorgente in un dato momento, permettendo a Jest di rilevare cambiamenti rispetto alle versioni precedenti e fornendo contesto aggiuntivo durante le code review.

Lo snapshot testing funziona solo con i componenti React?

I componenti React e React Native rappresentano un ottimo caso d'uso per i test di snapshot. Tuttavia, gli snapshot possono catturare qualsiasi valore serializzabile e dovrebbero essere utilizzati ogni volta che l'obiettivo è verificare la correttezza dell'output. Il repository di Jest contiene numerosi esempi di test dell'output di Jest stesso, dell'output della sua libreria di asserzioni e dei messaggi di log provenienti da varie parti del codice sorgente di Jest. Vedi un esempio di snapshot dell'output CLI nel repository di Jest.

Qual è la differenza tra snapshot testing e visual regression testing?

Lo snapshot testing e il visual regression testing sono due approcci distinti per testare le UI e servono scopi diversi. Gli strumenti di visual regression testing acquisiscono screenshot di pagine web e confrontano le immagini risultante pixel per pixel. Con lo snapshot testing, i valori vengono serializzati, memorizzati in file di testo e confrontati utilizzando un algoritmo diff. Ci sono diversi compromessi da considerare e abbiamo elencato le ragioni per cui abbiamo sviluppato lo snapshot testing nel blog di Jest.

Lo snapshot testing sostituisce i test unitari?

Lo snapshot testing è solo una delle oltre 20 asserzioni incluse in Jest. L'obiettivo non è sostituire i test unitari esistenti, ma fornire valore aggiuntivo e rendere il testing più semplice. In alcuni scenari, lo snapshot testing potrebbe potenzialmente eliminare la necessità di test unitari per specifiche funzionalità (ad esempio componenti React), ma possono anche coesistere.

Quali sono le prestazioni dello snapshot testing riguardo velocità e dimensione dei file generati?

Jest è stato riscritto con un focus sulle prestazioni, e lo snapshot testing non fa eccezione. Poiché gli snapshot vengono memorizzati in file di testo, questo approccio è veloce e affidabile. Jest genera un nuovo file per ogni file di test che richiama il matcher toMatchSnapshot. La dimensione degli snapshot è piuttosto contenuta: a titolo di esempio, la dimensione complessiva di tutti i file di snapshot nel codice sorgente di Jest stesso è inferiore a 300 KB.

Come si risolvono i conflitti nei file di snapshot?

I file di snapshot devono sempre rappresentare lo stato corrente dei moduli che coprono. Pertanto, se durante un merge tra due branch si verifica un conflitto nei file di snapshot, puoi risolverlo manualmente o aggiornare il file eseguendo Jest e verificando il risultato.

È possibile applicare i principi del test-driven development con lo snapshot testing?

Sebbene sia possibile scrivere manualmente i file di snapshot, generalmente non è un approccio pratico. Gli snapshot aiutano a rilevare cambiamenti nell'output dei moduli coperti dai test, piuttosto che guidare la progettazione del codice in fase iniziale.

La code coverage funziona con lo snapshot testing?

Sì, esattamente come con qualsiasi altro test.