Testando filas em projetos Laravel
Tags: php, laravel, phpunit, qa
Published: 2023-12-14 16:16:34

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.

{% embed https://github.com/jmgoncalves97/product-api %}

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.

{% embed https://dev.to/themattos/laravel-queue-uma-poderosa-alternativa-de-fila-para-projetos-php-3hfa %}

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
php artisan make:test Plataform1/ProductSyncTest
```

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.

```php
        Queue::fake();

        $productSync = new ProductSync();

        $queue = 'data_sync';

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

Agora, verificaremos se ProductSyncJob foi despachado para data_sync.

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

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

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

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

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

```php
        Bus::fake();

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

        Bus::assertBatched(function (PendingBatchFake $batch) {
            return count($batch->jobs) === 2;
        });
```

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

```php
<?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;
        });
    }
}
```

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.

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

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

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

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

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

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

```php
        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();
```

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.

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

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

        Product::truncate();

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

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

```php
    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']]);
    }
```

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.

```xml
        <env name="DB_CONNECTION" value="sqlite"/>
        <env name="DB_DATABASE" value=":memory:"/>
```

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

```php
    use RefreshDatabase;
```

Finalmente, vamos rodar nossos testes.

```java
php artisan test

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

![php artisan test](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hijb4xctzbnovt8ujwdm.png)

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.