Destino Dev

Blog

Testes de Jobs e Queues no Laravel: Queue::fake(), dispatch e assertions

Por John Pires · 19 de março de 2026

Contexto: quando você vai usar isso

O problema que os testes resolvem

Quando seu código dispara Jobs para serem processados em background — envio de e-mails, processamento de imagens, fila de exportação — você quer garantir que o comportamento ocorreu sem, necessariamente, executar o trabalho real durante o teste. Executar jobs em ambiente de teste aumenta tempo, cria dependências externas e pode deixar o teste frágil. É aí que entram os fakes e as assertions específicos para filas.

Por que usar Queue::fake() em vez de executar o job

Queue::fake() substitui a implementação real da fila pelo fake do framework. Isso permite verificar que um job foi empilhado, com quais parâmetros e em qual fila, sem executar o handle(). O teste foca na intenção ("dispatch foi chamado") e não na execução. Isso torna testes unitários e de integração mais rápidos e determinísticos.

As vantagens práticas são:

  • Testes mais rápidos (nenhum worker precisa processar a fila).
  • Isolamento: você testa apenas se o job foi enfileirado corretamente.
  • Assertions precisas: você pode checar quantidade, payload e fila.

Explicação visual com diagramas mentais

Diagrama simples: requisição > dispatch > fila real

HTTP Request -> Controller/Service -> dispatch(new Job($data)) -> Queue (redis/database) -> Worker -> Job::handle()

Esse é o caminho em produção. Em teste, queremos interromper esse fluxo logo após o dispatch para inspecionar se o job foi enfileirado corretamente.

Diagrama com Queue::fake()

HTTP Request -> Controller/Service -> dispatch(new Job($data)) -> Queue::fake() intercepta -> FakeQueue armazena metadados do Job -> Test faz assertions

O fake não executa Job::handle(). Ele apenas registra que o job foi empilhado. A partir daí, usamos assertPushed, assertPushedOn e outras assertions para validar comportamento.

Tip: pense no fake como um espião que só observa e anota as ações de dispatch, sem alterar o estado do sistema ou executar lógica pesada.

Primeiro exemplo simples

Contexto do exemplo

Exemplo prático minimalista: um Job que recebe um payload e escreve um log no handle(). No teste queremos apenas garantir que o job foi despachado com o payload correto.

Job simples e teste usando Queue::fake()

O código abaixo ilustra um Job básico, seguido do teste que usa Queue::fake(). Antes do bloco de código explico o que cada parte faz; depois explico o resultado esperado.

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class SimpleJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $payload;

    public function __construct($payload)
    {
        $this->payload = $payload;
    }

    public function handle()
    {
        \Log::info('SimpleJob handled', ['payload' => $this->payload]);
    }
}

O job acima é típico: implementa ShouldQueue e serializa o payload. Em produção o worker executaria handle() e gravaria no log.

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Support\Facades\Queue;
use App\Jobs\SimpleJob;

class SimpleJobTest extends TestCase
{
    public function test_dispatches_simple_job()
    {
        Queue::fake();

        SimpleJob::dispatch('hello');

        Queue::assertPushed(SimpleJob::class, function ($job) {
            return $job->payload === 'hello';
        });
    }
}

O teste acima faz três coisas principais:

  1. Faz Queue::fake() para interceptar dispatches.
  2. Dispara o job com SimpleJob::dispatch('hello').
  3. Usa Queue::assertPushed com uma closure para verificar o payload.

Resultado esperado: o teste passa se o job foi enfileirado com payload igual a 'hello'. Não há execução de lógica do handle().

Segundo exemplo mais realista

Contexto do cenário realistic

Imagine um endpoint que recebe uploads de imagem e, em vez de processar imediatamente, despacha um job para redimensionar e otimizar a imagem. No teste, queremos garantir que:

  • Um job específico foi enfileirado.
  • Foi enfileirado na fila correta (por exemplo, 'images').
  • Foi enfileirado a quantidade correta de vezes.

Job de processamento de imagem e teste

O job a seguir usa Storage para ler e gravar um arquivo. Em testes não precisamos que isso aconteça — apenas queremos garantir o dispatch e os metadados.

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Storage;

class ProcessImage implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $path;

    public function __construct(string $path)
    {
        $this->path = $path;
    }

    public function handle()
    {
        $contents = Storage::get($this->path);
        // processamento simplificado
        Storage::put('processed/' . $this->path, $contents);
    }
}

No controller, você poderia fazer:

// Controller snippet

public function upload(Request $request)
{
    $path = $request->file('image')->store('uploads');

    // Dispara job para fila 'images'
    ProcessImage::dispatch($path)->onQueue('images');

    return response()->json(['status' => 'queued']);
}

Agora o teste que valida tudo isso sem tocar no Storage real:

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Support\Facades\Queue;
use App\Jobs\ProcessImage;

class UploadImageTest extends TestCase
{
    public function test_upload_dispatches_process_image_on_images_queue()
    {
        Queue::fake();

        // Simula request que salva arquivo com caminho 'uploads/photo.jpg'
        $this->postJson('/upload', [/* form data simulated */]);

        Queue::assertPushedOn('images', ProcessImage::class);

        // Verifica que foi enfileirado exatamente uma vez
        Queue::assertPushed(ProcessImage::class, 1);
    }
}

Esse teste valida tanto a fila quanto a quantidade. Queue::assertPushedOn é útil quando você delega prioridades por filas.

Erros comuns de iniciante e como evitar

Executar o handle() acidentalmente

Um erro comum é não usar Queue::fake() e, na tentativa de validar side effects, acabar deixando o handle executar, causando efeitos colaterais (emails enviados, arquivos transformados). Evite isso usando o fake antes de acionar o código que faz dispatch.

AssertPushed com comparações equivocadas

Muitos iniciantes usam assertPushed(SimpleJob::class) sem validar a closure e depois esperam ver o payload. Isso pode passar sem garantir o payload correto. Use sempre a closure para validar atributos do job, ou assertPushedTimes/On com precisão.

Outras armadilhas:

  • Esquecer de fakear a queue no nível correto (por exemplo, usar Queue::fake() dentro de outro escopo que não cobre a execução do dispatch).
  • Confundir Bus::fake() com Queue::fake() — Bus é para comandos/handlers (Laravel Bus), Queue é para sistema de filas. Use o que seu código realmente dispara.
  • Depender de configurações de ambiente que alteram o driver de fila (sync vs redis). Em testes, Queue::fake() ignora o driver.
Prática: sempre coloque Queue::fake() como a primeira linha do teste, antes de qualquer ação que possa acionar o dispatch — incluindo chamadas a helpers ou listeners.

Exemplo de assertions úteis

Resumo das principais assertions e quando usar:

  • Queue::assertPushed(JobClass::class) — verifica que o job foi enfileirado pelo menos uma vez.
  • Queue::assertPushedTimes(JobClass::class, $times) — verifica número exato de enfileiramentos.
  • Queue::assertPushedOn('queue-name', JobClass::class) — verifica fila alvo.
  • Queue::assertNothingPushed() — garante que nada foi empilhado (útil após executar ações que não deveriam gerar jobs).
  • Usar closures na primeira arg de assertPushed para validar propriedades internas do job.

Conclusão prática

Resumo dos passos recomendados

Para testar dispatch de jobs no Laravel com segurança e velocidade, siga este roteiro prático:

  1. Coloque Queue::fake() no início do teste.
  2. Execute a ação que dispara o dispatch (chamada ao controller, service ou event).
  3. Use Queue::assertPushed, assertPushedOn e/ou assertPushedTimes para validar comportamento.
  4. Evite executar handle() nos testes que checam apenas dispatch; crie testes separados para o comportamento do handle() se necessário.

Últimas recomendações para quem está começando

Testes bem escritos separam intenção de execução. Use fakes para checar intenções ("disparar o job certo"), e testes de integração para checar efeitos ("o job processou e alterou o arquivo"). Rodar ambos garante cobertura e velocidade. Lembre-se de usar closures nas assertions para não deixar falsos positivos passarem.

Seguindo essas práticas você ganha confiança ao refatorar: se um job deixa de ser disparado você terá um teste rápido e direto apontando o problema. Se o job está sendo disparado, testes com Queue::fake() farão isso sem custo de infraestrutura.

Assuntos: Laravel, queues, Testes, phpunit, jobs

Leia também