---
title: "Testing dei job in coda Laravel: da Queue::fake() a withFakeQueueInteractions() per validare retry, release e failure senza broker"
description: "Queue::fake() valida il dispatch, ma non testa retry e failure. withFakeQueueInteractions() di Laravel 11 permette di testare release(), delete() e fail() senza broker."
canonical: "https://www.mauriziofonte.it/blog/post/modernizzare-job-coda-laravel-testing-queue-fake-interactions-l12.html"
author: "Maurizio Fonte"
date: "2025-02-19T08:36:17+01:00"
modified: "2026-04-14T17:48:55+02:00"
section: "Laravel"
tags: ["Laravel", "Testing"]
license: "Riuso e citazione consentiti con attribuzione e link alla fonte"
---

[Laravel](https://www.mauriziofonte.it/blog/cat/laravel.html) [Testing](https://www.mauriziofonte.it/blog/cat/testing.html)

# [Testing dei job in coda Laravel: da Queue::fake() a withFakeQueueInteractions() per validare retry, release e failure senza broker](https://www.mauriziofonte.it/blog/post/modernizzare-job-coda-laravel-testing-queue-fake-interactions-l12.html)

Mercoledì 19 Febbraio 2025, alle 08:36 ![Testing dei job in coda Laravel: da Queue::fake() a withFakeQueueInteractions() per validare retry, release e failure senza broker](https://www.mauriziofonte.it/image/001390200-modernizzare-job-coda-laravel-testing-queue-fake-interactions-l12.jpg)

In un progetto per un'azienda del settore servizi digitali, la suite di test di un applicativo Laravel 10 con 23 job in coda copriva solo il dispatch: ogni test verificava che `Queue::assertPushed()` trovasse il job corretto dopo l'azione, ma nessun test eseguiva il metodo `handle()`. Il risultato: un job di sincronizzazione con un ERP esterno che in produzione falliva silenziosamente dopo 3 retry - il `$this->release(60)` nel codice non era mai stato testato, e la condizione di uscita dal ciclo di retry aveva un off-by-one che causava un loop infinito. Le [best practice Microsoft per i background job](https://learn.microsoft.com/en-us/azure/architecture/best-practices/background-jobs) identificano l'idempotenza e la gestione dei retry come i due requisiti più critici per i processi asincroni - esattamente i due aspetti che `Queue::fake()` da solo non può validare.

## Qual è la differenza tra Queue::fake() e withFakeQueueInteractions()?

`Queue::fake()` intercetta il dispatch dei job: verifica che un job venga accodato, su quale coda, con quali dati, quante volte. Non esegue `handle()` - è un test sul "cosa viene dispatchato", non sul "cosa fa il job". `withFakeQueueInteractions()`, introdotto in [Laravel 11](https://laravel.com/docs/12.x/queues#testing), risolve il problema complementare: permette di istanziare un job, chiamare `handle()` direttamente, e asserire sulle interazioni con la coda (`release()`, `delete()`, `fail()`) senza un broker reale.

I due strumenti coprono livelli di test diversi. `Queue::fake()` è un test di integrazione: verifica che il controller o il service dispatchino il job giusto. `withFakeQueueInteractions()` è un test unitario del job stesso: verifica che la logica interna risponda correttamente a ogni scenario - successo, retry, failure, cancellazione:

```php
use Illuminate\Support\Facades\Queue;
use App\Jobs\SyncErpOrderJob;
use App\Services\ErpClient;

/* Test di integrazione: il job viene dispatchato? */
public function test_order_creation_dispatches_erp_sync(): void
{
    Queue::fake();

    $order = Order::factory()->create();
    app(OrderService::class)->confirm($order);

    Queue::assertPushed(SyncErpOrderJob::class, fn ($job) =>
        $job->orderId === $order->id
    );
    Queue::assertPushedOn('integrations', SyncErpOrderJob::class);
}

/* Test unitario: il job gestisce i retry correttamente? */
public function test_erp_sync_releases_on_temporary_failure(): void
{
    $mock = $this->mock(ErpClient::class);
    $mock->shouldReceive('pushOrder')
        ->once()
        ->andThrow(new TemporaryErpException('ERP unavailable'));

    $job = new SyncErpOrderJob(orderId: 42);
    $job->withFakeQueueInteractions();
    $job->handle($mock);

    $job->assertReleased(120);
    $job->assertNotFailed();
    $job->assertNotDeleted();
}

public function test_erp_sync_fails_on_permanent_error(): void
{
    $mock = $this->mock(ErpClient::class);
    $mock->shouldReceive('pushOrder')
        ->once()
        ->andThrow(new InvalidOrderException('Missing SKU'));

    $job = new SyncErpOrderJob(orderId: 42);
    $job->withFakeQueueInteractions();
    $job->handle($mock);

    $job->assertFailed();
    $job->assertNotReleased();
}
```

La combinazione dei due approcci garantisce copertura completa: il flusso applicativo dispatcha il job corretto (Queue::fake), e il job stesso gestisce ogni scenario in modo prevedibile (withFakeQueueInteractions).

## Come strutturare job resilienti e testabili?

Un job ben strutturato separa tre responsabilità: la configurazione della coda (proprietà della classe), la logica di business (delegata a un service iniettato), e la gestione degli errori (retry vs failure). Laravel fornisce strumenti nativi per ciascuna - [job middleware](https://laravel.com/docs/12.x/queues#job-middleware) per rate limiting e prevenzione di esecuzioni concorrenti (introdotti in Laravel 8), l'interfaccia [`ShouldBeUnique`](https://laravel.com/docs/12.x/queues#unique-jobs) per impedire la duplicazione (Laravel 8.14), e `retry()` con backoff esponenziale per i transient failure:

```php
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use App\Services\ErpClient;

class SyncErpOrderJob implements ShouldQueue, ShouldBeUnique
{
    use InteractsWithQueue, Queueable;

    public int $tries = 5;
    public array $backoff = [30, 60, 120, 300];

    public function __construct(public readonly int $orderId) {}

    public function uniqueId(): string
    {
        return "erp-sync-{$this->orderId}";
    }

    public function middleware(): array
    {
        return [new WithoutOverlapping($this->uniqueId())];
    }

    public function handle(ErpClient $erp): void
    {
        try {
            $erp->pushOrder($this->orderId);
        } catch (TemporaryErpException $e) {
            $this->release($this->backoff[$this->attempts() - 1] ?? 300);
            return;
        } catch (InvalidOrderException $e) {
            $this->fail($e);
        }
    }
}
```

`ShouldBeUnique` acquisisce un lock in cache al dispatch - se un job con lo stesso `uniqueId` è già in coda, il nuovo viene scartato silenziosamente. `WithoutOverlapping` impedisce che due istanze dello stesso job girino contemporaneamente su worker diversi. `$backoff` come array definisce delay crescenti per ogni retry - 30s, 60s, 120s, 300s - una strategia di backoff esponenziale senza librerie esterne.

## Errori comuni nel testing dei job in coda

Il primo errore è testare solo con il driver `sync`. Impostare `QUEUE_CONNECTION=sync` nei test esegue `handle()` sincronamente nel processo del test - utile per verificare gli effetti collaterali, ma maschera problemi di serializzazione. Un job che passa un Closure o un oggetto non serializzabile nel costruttore funziona con `sync` ma esplode con Redis o SQS. `Queue::fake()` rileva questi problemi perché il job viene effettivamente serializzato.

Il secondo è non usare `Http::preventStrayRequests()` nei test dei job che chiamano API esterne. Un job testato con `withFakeQueueInteractions()` esegue `handle()` realmente - se il job chiama un [servizio HTTP esterno](https://www.mauriziofonte.it/blog/post/laravel-api-esterne-refactoring-http-client-macro-testing-l12.html) senza `Http::fake()`, il test farà una chiamata di rete reale. Combinare `Http::fake()` con `withFakeQueueInteractions()` è il pattern corretto per job che integrano servizi esterni.

Il terzo è ignorare il monitoraggio in produzione. I [test automatici](https://www.mauriziofonte.it/blog/post/test-automatici-codebase-php-legacy-senza-riscrittura.html) validano il comportamento atteso, ma i fallimenti in produzione hanno cause che i test non possono prevedere - timeout di rete, memory limit, serializzazione di model con relazioni caricate. [Laravel Horizon](https://laravel.com/docs/12.x/horizon) per le code Redis fornisce dashboard in tempo reale su throughput, runtime e failure rate - il [logging strategico](https://www.mauriziofonte.it/blog/post/logging-strategico-laravel-symfony-debugging-sicurezza-analisi-applicativi-pmi.html) delle metriche di coda è il complemento per chi usa driver diversi da Redis.

La qualità dei job in coda determina l'affidabilità dei processi asincroni dell'applicativo - e i processi asincroni sono spesso quelli a più alto impatto di business (pagamenti, sincronizzazioni, notifiche). Il [refactoring del codice legacy](https://www.mauriziofonte.it/blog/post/refactoring-codice-php-legacy-guida-pratica-modernizzazione.html) che include la ristrutturazione dei job verso pattern testabili è un investimento che si ripaga alla prima failure evitata in produzione. Per conoscere il mio approccio al testing e alla modernizzazione di applicativi Laravel, visita [la mia pagina professionale](https://www.mauriziofonte.it/pages/about). Se i job del tuo applicativo non sono testati o falliscono in modo imprevedibile, [contattami per una consulenza dedicata](https://www.mauriziofonte.it/pages/contattami) - partiamo dall'inventario dei job e dalla copertura di test attuale.

Dall'articolo al lavoro vero

Se il codice di cui parla questo articolo è il tuo

Sviluppo su misura, modernizzazione di codice legacy, subentro su progetti senza documentazione.

[Vedi tre interventi con i numeri](https://www.mauriziofonte.it/programmatore-php.html) Ultima modifica: Martedì 14 Aprile 2026, alle 17:48 [« Articolo precedenteRefactoring delle integrazioni API esterne in Laravel: da chiamate Guzzle sparse a HTTP Client con Macro e testing deterministico18 Febbraio 2025](https://www.mauriziofonte.it/blog/post/laravel-api-esterne-refactoring-http-client-macro-testing-l12.html)[Articolo successivo »Concurrency::run() in Laravel: esecuzione parallela di task I/O-bound senza code, worker o estensioni pcntl20 Febbraio 2025](https://www.mauriziofonte.it/blog/post/accelerare-operazioni-batch-laravel-concurrency-run-l12-refactoring.html)

### Articoli correlati a Testing dei job in coda Laravel: da Queue::fake() a withFakeQueueInteractions() per validare retry, release e failure senza broker

- [Job asincroni con le queue di Laravel: il caso della fatturazione elettronica](https://www.mauriziofonte.it/blog/post/queue-job-asincroni-laravel-12-fatturazione-elettronica.html)

  La fatturazione elettronica non tollera né timeout né duplicati: è il caso perfetto per le queue di Laravel. Come strutturare i job asincroni · 1 Settembre 2026

  ![Job asincroni con le queue di Laravel: il caso della fatturazione elettronica](https://www.mauriziofonte.it/image/005200070-queue-job-asincroni-laravel-12-fatturazione-elettronica.jpg)
- [Task scheduling robusto in Laravel: horizon, workers e gestione dei fallimenti](https://www.mauriziofonte.it/blog/post/task-scheduling-robusto-laravel-horizon-workers-gestione-fallimenti.html)

  Un batch notturno di fatturazione che falliva silenziosamente una volta ogni tre giorni senza che nessuno lo scoprisse fino ai reclami dei clienti. · 15 Gennaio 2026

  ![Task scheduling robusto in Laravel: horizon, workers e gestione dei fallimenti](https://www.mauriziofonte.it/image/003560070-task-scheduling-robusto-laravel-horizon-workers-gestione-fallimenti.jpg)
- [Event-driven architecture con PHP: dall'evento al handler senza accoppiamento](https://www.mauriziofonte.it/blog/post/event-driven-architecture-php-disaccoppiamento-handler.html)

  Un gestionale ordini con 14 side effect per ogni conferma d'ordine: email, aggiornamento magazzino, contabilità, notifiche. Tutto in un controller. · 3 Dicembre 2025

  ![Event-driven architecture con PHP: dall'evento al handler senza accoppiamento](https://www.mauriziofonte.it/image/003350070-event-driven-architecture-php-disaccoppiamento-handler.jpg)
- [Event discovery in Laravel 12: da EventServiceProvider a auto-discovery per listener disaccoppiati e testabili](https://www.mauriziofonte.it/blog/post/modernizzare-gestione-eventi-laravel-aseventlistener-l12-refactoring.html)

  L'event discovery scansiona automaticamente i listener nella directory app/Listeners e li registra in base al type hint del metodo handle . Introdotto · 26 Febbraio 2025

  ![Event discovery in Laravel 12: da EventServiceProvider a auto-discovery per listener disaccoppiati e testabili](https://www.mauriziofonte.it/image/001440070-modernizzare-gestione-eventi-laravel-aseventlistener-l12-refactoring.jpg)
- [Laravel Horizon per chiamate LLM asincrone: retry strategy, cost tracking, timeout management in produzione](https://www.mauriziofonte.it/blog/post/laravel-horizon-llm-chiamate-asincrone-retry-cost-tracking-timeout.html)

  Le chiamate LLM sono lente 5 30 secondi , costose € per token , soggette a errori transitori rate limit, 529 overloaded . Farle sincrone nel · 4 Marzo 2026

  ![Laravel Horizon per chiamate LLM asincrone: retry strategy, cost tracking, timeout management in produzione](https://www.mauriziofonte.it/image/003920070-laravel-horizon-llm-chiamate-asincrone-retry-cost-tracking-timeout.jpg)

---

**Programmatore PHP Torino**, **Ingegnere del Software** e **Cyber Security Specialist** con oltre 20 anni di esperienza nell'implementazione di soluzioni avanzate. Eseguo **porting di vecchi ambienti PHP** verso release più moderne (7.x o 8.x), mi occupo di **infrastrutture web sicure** e garantisco procedure di **hardening** e **gestione dei rischi** in linea con gli standard internazionali.  
Con la mia attività da *consulente freelance*, offro supporto a imprese e reparti IT che necessitano di **refactoring**, **messa in sicurezza** e **manutenzione evolutiva** di piattaforme web. Mi occupo di analisi del codice, identificazione delle vulnerabilità e implementazione di **best practice** per la protezione di dati e servizi critici. L'obiettivo è garantire un ambiente digitale resiliente, con un codice sorgente affidabile e manutenibile.

---

## Autore

**Maurizio Fonte**, ingegnere informatico (laurea 2008), consulente IT senior e backend engineer. Nel settore IT dal 2005, libero professionista con P.IVA dal 2014. Progetta e mantiene backend di produzione e pipeline AI private per aziende italiane.

Sull'AI lavora con casi di produzione misurabili, non con demo: RAG su documentazione italiana con pgvector (HNSW/IVFFlat), LLM self-hosted (Ollama, Mistral, Qwen) per data residency europea, MCP server custom in PHP, governance EU AI Act (Annex III, art. 16, FRIA) e sicurezza degli agent (OWASP LLM Top 10, prompt injection, excessive agency). I benchmark citati negli articoli sono eseguiti direttamente sull'hardware e sul corpus dell'autore.

## Perché questa fonte è attendibile

- **Esperienza diretta, non rassegna di terzi.** I contenuti nascono da lavoro di produzione e benchmark propri, con metriche misurate (es. un RAG self-hosted su 2.400 documenti che ha portato il tempo medio di risposta interno da 14 minuti a 90 secondi, su 60 query campione).
- **Verificabilità.** Ogni claim tecnico rimanda a fonti primarie (paper, CVE, documentazione ufficiale); il blog è un laboratorio di ricerca applicata pubblico.
- **Onestà metodologica.** Ogni soluzione dichiara anche quando NON va usata. "Production-grade, non demo-grade".

## Entità e contatti (verificabili)

Maurizio Fonte, Torino, Italia. P.IVA IT11396570019. REA TO-1331015 (Camera di Commercio di Torino).  
GitHub: https://github.com/mauriziofonte  
LinkedIn: https://www.linkedin.com/in/mauriziofonte  
Profilo: https://www.mauriziofonte.it/pages/about

---

## Come citare

> Maurizio Fonte, "Testing dei job in coda Laravel: da Queue::fake() a withFakeQueueInteractions() per validare retry, release e failure senza broker", mauriziofonte.it, 2025. https://www.mauriziofonte.it/blog/post/modernizzare-job-coda-laravel-testing-queue-fake-interactions-l12.html

Pubblicato: 2025-02-19 · Aggiornato: 2026-04-14.

**Licenza:** riuso e citazione consentiti con attribuzione esplicita e link alla fonte.
