0Pricing
PHP Academy · Lezione

Mock e stub con Mockery

Isoli le unità con test double flessibili

Mock e stub con Mockery è una lezione PHP Academy gratuita su CoddyKit. Questa è la lezione 2 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento PHP Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso PHP Academy include 4 lezioni in totale.

Perché usare una libreria dedicata per i mock

PHPUnit include una propria API per i test double, ma Mockery offre una sintassi più fluida ed espressiva e funzionalità che mancano ai mock di PHPUnit: mock parziali, spy, matching flessibile degli argomenti e aspettative ordinate. Per il codice con molte collaborazioni, Mockery spesso risulta molto più leggibile. Questa lezione copre l'intero spettro dei test double e spiega quando utilizzare ciascun tipo.

composer require --dev mockery/mockery

Stub, mock o spy

Un vocabolario preciso evita test ambigui:

  • Stub — restituisce valori predefiniti; si verifica lo stato, cioè il risultato.
  • Mock — definisce aspettative sulle chiamate; si verifica il comportamento, cioè che la chiamata sia stata effettuata correttamente. Se un'aspettativa non viene soddisfatta, il test fallisce.
  • Spy — registra le chiamate e consente di verificarle a posteriori.

Regola pratica: preferisca gli stub per le query e mock o spy per i comandi.

Uno stub con allows()

Utilizzi Mockery::mock() e allows() (oppure shouldReceive()->andReturn()) per fornire valori restituiti predefiniti senza verificare che la chiamata sia avvenuta. Qui uno stub del gateway fornisce un tasso di cambio noto, così possiamo testare il calcolo in isolamento.

<?php
use Mockery;
use PHPUnit\Framework\TestCase;

final class ConverterTest extends TestCase
{
    public function test_converts_using_rate(): void
    {
        $rates = Mockery::mock(RateGateway::class);
        $rates->allows()->rateFor('USD', 'EUR')->andReturn(0.9);

        $sut = new Converter($rates);
        self::assertSame(90.0, $sut->convert(100.0, 'USD', 'EUR'));

        Mockery::close();
    }
}

Un mock con expects()

Quando l'interazione stessa è il contratto, ad esempio "un'email deve essere inviata esattamente una volta", utilizzi expects() oppure shouldReceive()->once(). Mockery verifica l'aspettativa durante Mockery::close(); una chiamata non effettuata fa fallire il test.

<?php
use Mockery;

$mailer = Mockery::mock(Mailer::class);
$mailer->expects()
       ->send(Mockery::type(Message::class))
       ->once();

$service = new SignupService($mailer);
$service->register('ada@example.com');

Mockery::close(); // fails here if send() was never called

Matcher degli argomenti

I matcher di Mockery consentono di rendere le aspettative flessibili o rigorose secondo necessità:

  • Mockery::any() — qualsiasi valore.
  • Mockery::type('string') / un nome di classe — verifica del tipo.
  • Mockery::on(fn($a) => ...) — predicato personalizzato.
  • Mockery::capture($var) — cattura l'argomento per asserzioni successive.
<?php
use Mockery;

$repo = Mockery::mock(UserRepo::class);
$repo->expects()
     ->save(Mockery::on(fn(User $u) => $u->isActive()))
     ->once()
     ->andReturnTrue();

Sequenze di valori restituiti e valori dinamici

È possibile definire più valori restituiti per chiamate successive oppure calcolare il valore restituito a partire dagli argomenti con andReturnUsing(). In questo modo si possono modellare logiche di retry, paginazione o collaboratori con stato.

<?php
use Mockery;

$api = Mockery::mock(HttpClient::class);
// First call throws, second succeeds (retry test):
$api->shouldReceive('get')
    ->twice()
    ->andThrow(new \RuntimeException('timeout'))
    ->andReturn('{"ok":true}');

// Or derive the return from the input:
$api->shouldReceive('echo')
    ->andReturnUsing(fn(string $in) => strtoupper($in));

Spy: verificare a posteriori

Uno spy inverte l'ordine: prima si esegue l'azione, poi si verifica il risultato. Mockery::spy() registra le chiamate; in seguito è possibile interrogarle con shouldHaveReceived(). Gli spy mantengono pulita la struttura Arrange-Act-Assert quando non si desidera appesantire la configurazione con aspettative predefinite.

<?php
use Mockery;

$logger = Mockery::spy(Logger::class);

$service = new PaymentService($logger);
$service->charge(500);

// Assertions happen AFTER the action:
$logger->shouldHaveReceived('info')
       ->with(Mockery::pattern('/charged 500/'))
       ->once();

Mockery::close();

Mock parziali

A volte si desidera un oggetto reale con un solo metodo sovrascritto: un mock parziale. makePartial() di Mockery richiama i metodi reali, tranne quelli per cui sono state impostate aspettative. Lo utilizzi con moderazione: un ricorso massiccio ai mock parziali segnala spesso che una classe svolge troppe responsabilità.

<?php
use Mockery;

$report = Mockery::mock(Report::class)->makePartial();
// Only stub the slow/external method; the rest runs for real
$report->shouldReceive('fetchRawData')->andReturn(['a', 'b', 'c']);

// real summarize() runs, using the stubbed data
$summary = $report->summarize();

Non esegua il mock di ciò che non possiede

Un principio fondamentale del testing è evitare di creare mock direttamente per le classi di terze parti. Le loro API possono cambiare e il mock può allontanarsi silenziosamente dalla realtà. Le racchiuda invece dietro una propria interfaccia e crei il mock di quella. Il mock verificherà quindi il suo contratto, mentre un test di integrazione dell'adapter verificherà il binding reale.

<?php
interface PaymentGateway {            // you own this
    public function charge(int $cents, string $token): string;
}

final class StripeGateway implements PaymentGateway {
    public function __construct(private \Stripe\StripeClient $client) {}
    public function charge(int $cents, string $token): string {
        return $this->client->paymentIntents->create([/* ... */])->id;
    }
}
// Tests mock PaymentGateway, never \Stripe\StripeClient directly.

Chiudere sempre e verificare i conteggi

Due operazioni indispensabili:

  • Chiami Mockery::close() in tearDown() (oppure utilizzi il trait MockeryPHPUnitIntegration), così le aspettative vengono effettivamente verificate e le variabili globali ripulite.
  • Utilizzi conteggi espliciti (once(), times(n), never()): i mock vaghi lasciano passare i bug.
<?php
use PHPUnit\Framework\TestCase;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;

final class OrderServiceTest extends TestCase
{
    use MockeryPHPUnitIntegration; // auto-calls Mockery::close()

    public function test_does_not_refund_paid_orders(): void
    {
        $gw = \Mockery::mock(PaymentGateway::class);
        $gw->shouldReceive('refund')->never();
        // ... exercise SUT ...
    }
}

Aspettative ordinate

Talvolta l'ordine delle chiamate fa parte del contratto: è necessario chiamare beginTransaction() prima di commit(). ordered() di Mockery impone la sequenza e fa fallire il test se le chiamate arrivano nell'ordine sbagliato. Lo utilizzi solo quando l'ordine è realmente importante: specificarlo eccessivamente rende i test fragili.

<?php
use Mockery;

$tx = Mockery::mock(Transaction::class);
$tx->shouldReceive('begin')->once()->ordered();
$tx->shouldReceive('commit')->once()->ordered();

$service = new TransferService($tx);
$service->run();

Mockery::close(); // fails if commit() happened before begin()

Controllo rapido

Stub o mock: quale utilizzare e quando?

Riepilogo

Ha imparato a usare i test double di Mockery:

  • Stub (allows) per le query; mock (expects) per i comandi; spy per le asserzioni a posteriori.
  • I matcher degli argomenti (type, on, capture) regolano il livello di rigore.
  • Le sequenze di valori restituiti e andReturnUsing modellano collaboratori con stato o dinamici.
  • I mock parziali sovrascrivono singoli metodi; li utilizzi con moderazione.
  • Non crei mock per ciò che non possiede: racchiuda le terze parti dietro una propria interfaccia.
  • Chiami sempre Mockery::close() e verifichi conteggi espliciti delle chiamate.

Prossimo argomento: testing di integrazione e funzionale.

Domande Frequenti

La lezione «Mock e stub con Mockery» è gratuita?

Sì — il testo completo di «Mock e stub con Mockery» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso PHP Academy, passa a CoddyKit PRO. Il corso PHP Academy include 4 lezioni in totale.

Cosa imparerò in «Mock e stub con Mockery»?

Isoli le unità con test double flessibili Eserciti PHP Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare PHP Academy?

Non è richiesta alcuna esperienza precedente. PHP Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 2 di 4.

Quanto tempo richiede la lezione «Mock e stub con Mockery»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione PHP Academy?

Sì. Ogni lezione PHP Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Il flusso di lavoro dello sviluppo guidato dai test
  2. Mock e stub con Mockery
  3. Test di integrazione e funzionali
  4. Mutation testing con Infection
← Torna a PHP Academy