Mock, Stub e Spy nei Test PHP — Isolare il Codice Senza Falsificare la Realtà
> cd .. / HUB_EDITORIALE > Visualizza in Inglese
Sviluppo di siti web

Mock, Stub e Spy nei Test PHP — Isolare il Codice Senza Falsificare la Realtà

[2026-07-13] Author: Ing. Calogero Bono
> condividi
Zenithby Meteora Web Il sistema operativo della tua attività. Social, clienti, prenotazioni e fatture in un'unica piattaforma. Palestre, barber, professionisti. Scopri Zenith Demo gratis · senza carta

Il tuo test sta passando ma il sistema in produzione si rompe lo stesso? Hai appena scoperto di aver testato un falso. Senza doppi di test ben usati, non stai testando il codice vero: stai testando una sua foto sbiadita.

Noi, di Meteora Web, ci abbiamo sbattuto la testa. Gestiamo piattaforme Laravel, un ERP di abbigliamento, e-commerce con decine di integrazioni. Ogni volta che un test fatica a isolare una dipendenza esterna — un API call, un database, un file system — il rischio è duplice: o il test diventa lento e fragile, o diventa inutile. I doppi di test (mock, stub, spy) sono la risposta. Ma vanno usati al posto giusto.

Questa guida parte da un problema concreto: come isolare il codice che stai testando senza mentire a te stesso. Non un elenco di definizioni accademiche. Casi reali, codice che puoi copiare, decisioni da prendere oggi.

Perché isolare il codice con i doppi di test invece di testare tutto insieme?

Immagina di dover testare una funzione che calcola lo sconto su un ordine e chiama un servizio esterno di spedizione per ottenere il costo. Se durante il test chiamiamo il vero servizio di spedizione, succede questo:

  • il test dipende dalla rete (lento, 2-10 secondi a chiamata)
  • il test dipende dal server remoto (se è giù, il test fallisce senza motivo)
  • il test costa denaro (API a pagamento contate a chiamata)

Non stai testando la logica di sconto, stai testando l'infrastruttura. I doppi di test risolvono: sostituisci il servizio di spedizione con un oggetto finto che risponde in millisecondi e non fallisce mai. Ma attenzione: isolare troppo porta a test che passano anche se il sistema reale è rotto. Il punto è isolare solo le dipendenze esterne, non la logica interna.

Errori comuni nell'isolare il codice

Il primo errore: mockare tutto. Se mocki anche il Repository dell'utente perché non vuoi toccare il database, il test perde di validità. La logica di business nasce proprio dall'interazione con quei dati. Noi preferiamo isolare solo i servizi esterni (API, mail, file system) e lasciare reali le interazioni col database quando il test è veloce e controllato. Veniamo da una PMI: il tempo è denaro, test lenti = test che nessuno esegue.

Sponsored Protocol

Cosa fare adesso: nel prossimo test che scrivi, chiediti: "Questa dipendenza è sotto il mio controllo? Se è esterna, la sostituisco. Se è interna (database locale, cache, file locale), la tengo reale."

Quale doppio di test usare: stub, mock o spy?

La confusione tra questi tre è il motivo principale per cui i test diventano fragili. Ecco la differenza operativa, con esempi in PHPUnit.

Stub: quando vuoi una risposta predefinita

Uno stub è un oggetto che fornisce dati fissi. Lo usi quando non ti interessa come viene chiamato, ma solo che restituisca un valore per far proseguire il test.

// Stub per un servizio di spedizione
$shippingStub = $this->createStub(ShippingService::class);
$shippingStub->method('getCost')->willReturn(12.50);

$calculator = new DiscountCalculator($shippingStub);
$total = $calculator->applyDiscount(100, 'VIP');
$this->assertEquals(87.50, $total); // 100 - 12.50 = 87.50? Whoa, dipende dalla logica

Nota: lo stub non verifica quante volte getCost viene chiamato. Restituisce 12.50 ogni volta. Utile per isolare.

Mock: quando devi verificare le interazioni

Un mock è come uno stub, ma con aspettative sui metodi chiamati. Lo usi quando devi assicurarti che la funzione abbia chiamato correttamente una dipendenza. Esempio classico: invio email dopo un acquisto.

Sponsored Protocol

$mailerMock = $this->createMock(Mailer::class);
$mailerMock->expects($this->once())
    ->method('send')
    ->with('cliente@example.com', 'Ordine confermato');

$checkout = new Checkout($mailerMock);
$checkout->process($order);

Se il metodo send non viene chiamato esattamente una volta con quei parametri, il test fallisce. Potente, ma da usare con parsimonia: troppe expectations rendono i test fragili a refactoring innocui.

Spy: registra le chiamate per verifiche successive

Uno spy è un mock più flessibile. Non ha expectations predefinite: registra cosa succede e poi lo analizzi. PHPUnit non ha uno spy nativo, ma puoi crearlo con un mock senza expectations e poi usare assertions.

$spy = $this->createMock(Logger::class);
// nessun expects
$system = new System($spy);
$system->doSomething();

// Verifica dopo
$this->assertTrue($spy->logCalled); // se Logger ha una proprietà logCalled

In pratica, noiosi da scrivere a mano. Noi usiamo quasi sempre mock. Gli spy li riserviamo a situazioni dove l'ordine delle chiamate conta e non vogliamo overspecify.

Cosa fare adesso: prendi un test esistente e identifica ogni dipendenza esterna. Per ognuna, decidi: stub per dati, mock per interazioni, spy per sequenze complesse. Nessun altro tipo.

Mock nei test PHPUnit: configurazione minima e assert realistici

PHPUnit offre createMock e createStub dalla versione 9. La differenza è suttile: createMock crea un mock che di default chiama i metodi reali? No, di default restituisce null per ogni metodo. createStub è identico ma più esplicito. Noi consigliamo createStub per dati e createMock per expectations.

Esempio concreto: Repository e servizio esterno

Supponiamo di avere un OrderProcessor che salva l'ordine e chiama un servizio di pagamento. Vogliamo testare solo la logica di salvataggio.

Sponsored Protocol

class OrderProcessorTest extends TestCase
{
    public function test_it_saves_order_and_calls_payment()
    {
        $paymentMock = $this->createMock(PaymentGateway::class);
        $paymentMock->expects($this->once())
            ->method('charge')
            ->with(100.0)
            ->willReturn(true);

        $repositoryStub = $this->createStub(OrderRepository::class);
        $repositoryStub->method('save')->willReturn(123);

        $processor = new OrderProcessor($paymentMock, $repositoryStub);
        $result = $processor->process(100.0);

        $this->assertEquals(123, $result);
    }
}

Attenzione: stiamo testando che il processor chiami payment una volta e salvi l'ordine. Non testiamo il comportamento di PaymentGateway reale. Questo va bene perché PaymentGateway è una dipendenza esterna (API Stripe).

Evita i mock eccessivi

Se hai bisogno di mock per tre classi diverse in un singolo test, probabilmente stai testando troppo. Isola solo il confine: la classe sotto test e un collaboratore esterno. Gli altri dovrebbero essere oggetti reali o value objects.

Cosa fare adesso: nel tuo prossimo test, limita i mock a massimo due. Se superi, ristruttura il codice. Un test che richiede tre mock è un campanello d'allarme sul design.

Test isolation senza falsificare la realtà: quando NON usare i doppi

I doppi di test sono strumenti chirurgici, non mazze. Ci sono casi in cui usarli è sbagliato:

  • Value objects: oggetti come Money, Address, Email. Non mockarli mai. Creali reali.
  • Repository interni: se usi un database locale in-memory (SQLite in memoria), non mockare il Repository. Scrivi test di integrazione leggeri.
  • Servizi con logica interna critica: se il servizio di calcolo tasse contiene business logic, non mockarlo. O lo testi integrato o lo testi in unità separata.

Noi abbiamo visto progetti dove ogni test mocka anche il repository del catalogo: il risultato è una suite di test che passa sempre perché non tocca mai i dati veri. Quando in produzione un campo di prodotto viene letto come null, il test non lo scopre. Il test isolation non è un fine, è un mezzo. Il fine è sapere se il codice funziona.

Sponsored Protocol

Cosa fare adesso: controlla la tua suite di test: trovi mock di classi semplici come DTO o Entity? Rimuovili. Usa oggetti reali con array di dati fittizi.

Come impostare una strategia di test doubles per una PMI che sviluppa in PHP

Lavoriamo tutti i giorni con Laravel e PHPUnit. Ecco la nostra regola pratica, nata da anni di errori:

  1. Identifica i confini del sistema: tutto ciò che è fuori dal tuo processo PHP (API, file system, email, tempo) merita un doppio.
  2. Usa stub per i dati di risposta (es. API che torna JSON).
  3. Usa mock per le interazioni obbligate (es. garantire che una mail venga spedita).
  4. Non usare spy se non per debugging; preferisci mock espliciti.
  5. Mantieni i test di unità veloci sotto i 100ms. Se un test con mock supera i 200ms, probabilmente stai mockando male o testando integrazione.
  6. Usa un trait o factory per creare mock di servizi comuni, così eviti duplicazione di configurazione.

Noi, di Meteora Web, abbiamo costruito una libreria interna per gestire mock di servizi esterni (API spedizione, pagamento, SMS). Riduce il boilerplate del 70% e mantiene i test leggibili. Il segreto? Ogni mock deve essere facile da capire e facile da modificare.

Sponsored Protocol

Cosa fare adesso: apri il tuo progetto Laravel e crea un helper in tests/Helpers/MockHelper.php con un metodo per creare il mock del tuo servizio più usato. Esempio:

function mockShippingService(float $cost = 10.0): ShippingService
{
    $stub = \PHPUnit\Framework\TestCase::createStub(ShippingService::class);
    $stub->method('getCost')->willReturn($cost);
    return $stub;
}

Poi usa questo helper in tutti i test che hanno bisogno di isolare la spedizione.

Cosa fare adesso per migliorare i tuoi test con mock stub spy

Sintesi operativa per finire la guida e mettere subito in pratica:

  1. Rivedi un test che fallisce spesso a causa di dipendenze esterne. Sostituisci quella dipendenza con un mock o stub usando createMock o createStub di PHPUnit.
  2. Limita i mock a uno per test per la maggior parte dei casi. Se ne servono di più, valuta se il codice ha troppe responsabilità.
  3. Aggiungi un test che verifica una interazione chiave (es. chiamata API o email) con un mock e expects. Fallo fallire intenzionalmente per vedere se funziona.
  4. Controlla il tempo di esecuzione della suite: se un test con mock impiega più di 200ms, probabilmente stai facendo qualcosa di sbagliato (es. caricando l'app intera).
  5. Leggi la documentazione ufficiale PHPUnit sui test doubles per approfondire le opzioni avanzate: PHPUnit Test Doubles.

E ricorda: il test isolation serve a proteggere il vero codice, non a creare una bolla. Usa mock, stub e spy con la stessa attenzione con cui scegli un fornitore per la tua azienda: solo quando serve, con criterio.

Per approfondire la strategia completa di testing, consulta la nostra guida pillar sul testing del software.

> condividi
Ing. Calogero Bono

> AUTHOR_EXTRACTED

Ing. Calogero Bono

Ingegnere informatico, fondatore di Meteora Web e Zenith OS. System administrator e progettista di piattaforme, app e CMS proprietari, con esperienza in sviluppo full-stack, marketing digitale ed ecosistema Google.
[ Read Full Dossier ]

> METEORA_WEB // WEB AGENCY

Costruiamo la presenza digitale che la tua azienda merita.

Siti web, social, pubblicità online, e-commerce e hosting performante: ingegnerizzati con metodo da ingegneri informatici a Sciacca, per tutta Italia.

> MW_JOURNAL

> READ_ALL()