DEV Community

Jonathan Gonçalves
Jonathan Gonçalves

Posted on

Testando filas em projetos Laravel

Os testes unitários desempenham um papel crucial no desenvolvimento de software, garantindo que cada parte do código funcione conforme o esperado. Em projetos Laravel, onde a utilização de filas é comum para processamento em segundo plano, é essencial garantir que essas filas sejam testadas de forma adequada.

Vamos implementar testes na fila de sincronização de dados do projeto product-api.

Product API

Rode o composer install:

composer install
Enter fullscreen mode Exit fullscreen mode

Após a instalação, o script ./init.sh será chamado para:

  • Instalar o MySQL
  • Criar o banco usado pela API.
  • Criar no banco o usuário utilizado pela API.
  • Rodar as migrações.
  • Gerar o swaager.json
  • Rodar os testes.
  • Startar a aplicação.

Laravel Logo

Build Status Total Downloads Latest Stable Version License

About Laravel

Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:

Laravel is accessible, powerful, and provides tools required for large, robust applications.

Learning Laravel

Laravel has the most extensive and thorough documentation and video…




Antes de começar, confira meu artigo onde explico como implementar o Laravel Queue em seu projeto, seguindo padrões para manter seu código testável.

No meu caso, optei por criar os testes após a implementação da fila. Contudo, vale destacar que a abordagem inversa, onde os testes são criados antes da implementação, também é válida e pode ser adotada conforme a preferência em seu projeto.

Crie um novo teste com o comando artisan make:test. Para fins de organização, criarei meus testes dentro da pasta Plataform1, pois meu job faz sinc de produtos com esse módulo.

php artisan make:test Plataform1/ProductSyncTest
Enter fullscreen mode Exit fullscreen mode

O primeiro método que vamos criar, será responsável por testar o despache do nosso job para a fila. Não estranhe o tamanho do nome do método, pois prefiro que seja descritivo e claro em relação à sua funcionalidade.

Vamos usar umaQueue fake para testar os despaches e vamos despachar os jobs.

        Queue::fake();

        $productSync = new ProductSync();

        $queue = 'data_sync';

        ProductSyncJob::dispatch($productSync)->onQueue($queue);
Enter fullscreen mode Exit fullscreen mode

Agora, verificaremos se ProductSyncJob foi despachado para data_sync.

Queue::assertPushedOn($queue, ProductSyncJob::class);
Enter fullscreen mode Exit fullscreen mode

Vamos despachar mais dois jobs e fazer uma nova afirmação.

        Queue::bulk([
            new ProductSyncJob($productSync),
            new ProductSyncJob($productSync),
        ], '', $queue);

        Queue::assertPushed(ProductSyncJob::class, 3);
Enter fullscreen mode Exit fullscreen mode

Caso você implemente o despache de jobs com o facade Bus, também poderá testa-lo.

        Bus::fake();

        Bus::batch([
            new ProductSyncJob($productSync),
            new ProductSyncJob($productSync),
        ])->onQueue($queue)->dispatch();

        Bus::assertBatched(function (PendingBatchFake $batch) {
            return count($batch->jobs) === 2;
        });
Enter fullscreen mode Exit fullscreen mode

A implementação do nosso primeiro teste ficará assim.

<?php

namespace Tests\Feature\Plataform1;

use App\Jobs\Plataform1\ProductSyncJob;
use App\Services\Plataform1\ProductSync;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Testing\Fakes\PendingBatchFake;
use Tests\TestCase;

class ProductSyncTest extends TestCase
{
    public function test_dispatching_the_product_sync_job_to_the_queue(): void
    {
        Queue::fake();

        $productSync = new ProductSync();

        $queue = 'data_sync';

        ProductSyncJob::dispatch($productSync)->onQueue($queue);

        Queue::assertPushedOn($queue, ProductSyncJob::class);

        Queue::bulk([
            new ProductSyncJob($productSync),
            new ProductSyncJob($productSync),
        ], '', $queue);

        Queue::assertPushed(ProductSyncJob::class, 3);

        Bus::fake();

        Bus::batch([
            new ProductSyncJob($productSync),
            new ProductSyncJob($productSync),
        ])->onQueue($queue)->dispatch();

        Bus::assertBatched(function (PendingBatchFake $batch) {
            return count($batch->jobs) === 2;
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

Você pode ajustar o teste de acordo com a necessidade do seu projeto, Laravel é um framework altamente testável e rico em recursos de teste.

Agora, vamos criar um teste chamado test_product_sync para o serviço ProductSync que injetamos em ProductSyncJob. Conhecer a implementação de ProductSync é fundamental para o entendimento do próximo teste. Você pode verificar sua implementação no artigo ou repositório mencionado anteriormente.

Vamos pegar a url que recebe as requisições e criar dois arrays de produtos.

        $plataform1Url = config('integration.plataform1')['api']['url'];

        $data1 = Plataform1Product::factory()
            ->count(10)
            ->make()
            ->toArray();

        $data2 = Plataform1Product::factory()
            ->count(10)
            ->make()
            ->toArray();
Enter fullscreen mode Exit fullscreen mode

Agora, criaremos uma closure que retornará nosso response baseado nos parâmetros.

        $json = fn ($page, $nextPage) => [
            'next_page' => $nextPage,
            'data' => match ($page) {
                1 => $data1,
                2 => $data2,
            }
        ];
Enter fullscreen mode Exit fullscreen mode

Então, vamos mockar os requests recebidos no endpoint, instanciar ProductSync e chamar o método execute.

        Http::fake([
            $plataform1Url.'/v1/products?page=1' => Http::response($json(1, 2)),
            $plataform1Url.'/v1/products?page=2' => Http::response($json(2, null)),
        ]);

        $productSync = new ProductSync();

        $productSync->execute();
Enter fullscreen mode Exit fullscreen mode

Faremos três afirmações, que nossa tabela de produto tem um produto de $data1, um produto de $data2 e que após truncar, o produto estará ausente.

        $this->assertDatabaseHas('product', ['id' => $data1[0]['id']]);

        $this->assertDatabaseHas('product', ['id' => $data2[0]['id']]);

        Product::truncate();

        $this->assertDatabaseMissing('product', ['id' => $data1[0]['id']]);
Enter fullscreen mode Exit fullscreen mode

A implementação do nosso segundo teste ficará assim.

    public function test_product_sync(): void
    {
        $plataform1Url = config('integration.plataform1')['api']['url'];

        $data1 = Plataform1Product::factory()
            ->count(10)
            ->make()
            ->toArray();

        $data2 = Plataform1Product::factory()
            ->count(10)
            ->make()
            ->toArray();

        $json = fn ($page, $nextPage) => [
            'next_page' => $nextPage,
            'data' => match ($page) {
                1 => $data1,
                2 => $data2,
            }
        ];

        Http::fake([
            $plataform1Url.'/v1/products?page=1' => Http::response($json(1, 2)),
            $plataform1Url.'/v1/products?page=2' => Http::response($json(2, null)),
        ]);

        $productSync = new ProductSync();

        $productSync->execute();

        $this->assertDatabaseHas('product', ['id' => $data1[0]['id']]);

        $this->assertDatabaseHas('product', ['id' => $data2[0]['id']]);

        Product::truncate();

        $this->assertDatabaseMissing('product', ['id' => $data1[0]['id']]);
    }
Enter fullscreen mode Exit fullscreen mode

Antes de executarmos os testes, realizaremos algumas configurações para que o teste utilize um banco SQLite em memória.

Em phpunit.xml adicione as seguintes variáveis.

        <env name="DB_CONNECTION" value="sqlite"/>
        <env name="DB_DATABASE" value=":memory:"/>
Enter fullscreen mode Exit fullscreen mode

Dentro de nossa classe de teste, use a trait RefreshDatabase.

    use RefreshDatabase;
Enter fullscreen mode Exit fullscreen mode

Finalmente, vamos rodar nossos testes.

php artisan test

// opcionalmente, você pode passar o caminho do arquivo que deseja testar
php artisan test path/to/file
Enter fullscreen mode Exit fullscreen mode

php artisan test

Neste artigo, exploramos a importância dos testes em filas de projetos Laravel, destacando como eles fortalecem a confiabilidade das aplicações. Ao adotar práticas de teste eficazes, garantimos que a execução assíncrona e a gestão de filas sejam mais consistentes. A integração de testes sólidos contribui para a construção de sistemas mais resilientes em ambientes de produção.

Top comments (0)