Tag: testing, integration, api, database, mock, testcontainers
Livello: intermedio
TL;DR: Verifica che i diversi moduli e componenti di un'applicazione funzionino correttamente quando combinati, testando le interazioni reali con database, file system e servizi di rete.
Testa l'interazione tra due o più unità software per identificare difetti di interfacciamento.
Identifica bug legati a schemi di database, contratti API, serializzazione dei dati e latenza.
Richiede un ambiente di test vicino a quello di produzione, preferendo dipendenze reali a quelle simulate.
L'uso di Testcontainers o database in-memory è lo standard per testare l'accesso ai dati in isolamento.
I mock e gli stub si usano con parsimonia, riservati a servizi esterni inaffidabili, lenti o a pagamento.
Il ciclo di vita dello stato è critico: ogni test deve preparare i propri dati (setup) e pulirli alla fine (teardown).
Eseguire i test all'interno di transazioni con rollback automatico accelera l'esecuzione e previene inquinamento.
I test di integrazione sono tipicamente più lenti degli unit test ma più affidabili per garantire il comportamento del sistema.
Q: Qual è la differenza principale tra unit test e integration test nel tuo flusso di lavoro?R: Uso gli unit test per validare la logica di business isolata mockando tutto. Uso gli integration test per verificare che il mio codice dialoghi correttamente con il database e altri servizi, usando componenti reali.
Q: Come gestisci lo stato del database per evitare che i test interferiscano tra loro?R: Avvio un database pulito tramite Testcontainers. Per ogni test apro una transazione e al termine eseguo un rollback, assicurandomi che lo stato torni intatto per il test successivo.
Q: Quando ha senso creare un mock in un test di integrazione?R: Mocko solo le API di terze parti su cui non ho controllo, come un gateway di pagamento, usando strumenti come WireMock per simulare in modo affidabile le risposte HTTP reali.
Flakiness: test che falliscono casualmente a causa di race condition o dipendenze asincrone non attese correttamente.
Inquinamento dello stato (State leakage): dati sporchi lasciati nel database da un test precedente che fanno fallire i test successivi.
Over-mocking: simulare troppe dipendenze, finendo per testare il mock stesso invece della reale interazione tra i componenti.
Tempi di esecuzione eccessivi: lanciare intere migrazioni o build di container per ogni singolo test invece di riutilizzare l'infrastruttura.
// Esempio Jest + Supertest per testare una API Node.js con database reale
import request from 'supertest';
import app from '../src/app';
import db from '../src/db';
beforeAll(async () => {
// Inizializza lo schema prima della suite
await db.migrate.latest();
});
afterAll(async () => {
// Chiude la connessione per evitare leak
await db.destroy();
});
describe('POST /users', () => {
it('creates a new user and persists it in the database', async () => {
// Esecuzione chiamata reale all'endpoint
const response = await request(app)
.post('/users')
.send({ email: 'test@example.com', name: 'Test User' });
expect(response.status).toBe(201);
expect(response.body.id).toBeDefined();
// Verifica diretta sul database per conferma integrazione
const userInDb = await db('users').where({ id: response.body.id }).first();
expect(userInDb.email).toBe('test@example.com');
});
});