Closures ou High Order Tests, qual abordagem utilizar no PestPHP?
pest
O conceito de High Order Tests introduzido no PestPHP torna o código bastante elegante e expressivo mas será que existe alguma diferença ao utilizar este conceito ao invés de closures? A ideia deste post é mostrar em qual momento é interessante se utilizar de cada abordagem.
O que são high order tests?
High Order Tests conforme é definido pelo próprio autor do Pest (em tradução livre): são "atalhos" para ações comuns e, caso uma closure não seja provida, o encadeamento de métodos cuidará de criar uma closure para você.
Ou seja, de forma transparente o encadeamento de métodos é "traduzido" no final das contas e à partir disso as asserções são realizadas.
Mas será que existe alguma vantagem ou desvantagem em utilizar High Order Tests?
Quando utilizar
A vantagem mais clara em sua utilização é a legibilidade e elegância, um caso de teste consegue ser bastante expressivo utilizando high order tests:
$email = '[email protected]';
test('email should be from Gmail')
->expect($email)
->toEndWith('@gmail.com');
// ou
it('should be a Gmail account')
->assertStringContainsString('@gmail.com', $email);
Outra vantagem é que quando utilizamos asserções que são possíveis de serem resolvidas por si só também conseguimos economizar algumas linhas de código utilizando high order tests.
Além disso, quando bem escritos, muitas vezes será possível que ao bater o olho em um caso de teste e conseguiremos entender o que é que está acontecendo ali.
Quando não utilizar
Apesar de ser possível resolver muita coisa com high order tests devemos levar em consideração seu escopo.
Ao utilizar esta abordagem o escopo do caso de teste é o mesmo do arquivo atual, ou seja, apenas o escopo global. Também não haverá acesso a variável $this
que nas closures se refere a classe que foi feita a ligação (bind) no arquivo Pest.php
, ou seja, não há acesso aos itens da classe TestCase
e por consequência, caso esteja utilizando a classe Tests\TestCase
do Laravel, não é feito o bootstrap do framework e qualquer item que dependa disso não poderá ser utilizado.
Uma factory é um exemplo de item que precisa passar pelo bootstrap do framework para ser resolvida caso esteja utilizando a biblioteca Faker para preencher algum atributo.
Vamos criar e executar um teste que utiliza a factory de usuários que vem por padrão no Laravel:
use App\Models\User;
$user = User::factory()
->make();
test('user email can not be null')
->assertNotEmpty($user->email);
php artisan test
E neste momento, mesmo que tenha sido feita a ligação (bind) da classe Tests\TestCase
no arquivo Pest.php
será gerado um erro de execução indicando um InvalidArgumentException
:
Ao tentar utilizar a biblioteca Faker foi gerado o erro Unknown formatter
.
A biblioteca Faker depende de um formatter mas como por padrão o Laravel já cria a instância do Faker automagicamente para nós, este formatter é passado de forma transparente.
Como no contexto de high order tests não é feito o bootstrap do framework as dependências da factory e da faker não foram resolvidas.
Ao utilizar testes com closures não teremos este problema pelo fato de que a classe Tests\TestCase
fará o bootstrap do framework no contexto atual:
test('user email can not be null', function () {
$user = User::factory()
->make();
$this->assertNotEmpty($user->email);
});
Veja que o teste é exatamente o mesmo e nos livramos do erro!
Outra possível desvantagem no contexto de high order tests é que como não temos acesso a variável $this
ficamos incapazes de utilizar propriedades adicionadas em métodos de setup, como é o exemplo de beforeEach
e afterEach
:
use App\Models\User;
beforeEach(function () {
$this->user = User::factory()
->make();
});
test('user email can not be null')
->assertNotEmpty($this->user->email);
Ao executar o teste acima teremos um erro Using $this when not in object context
sobre o contexto atual:
Porém, se ao invés de high order tests, utilizarmos closures, daremos cabo deste erro:
use App\Models\User;
beforeEach(function () {
$this->user = User::factory()
->make();
});
test('user email can not be null', function () {
$this->assertNotEmpty($this->user->email);
});
Conclusão
Apesar de elegantes, os high order tests não resolvem todos os cenários que uma suíte de testes pode precisar mas felizmente o PestPHP está preparado para isso! Com as closures temos maior poder e controle do ambiente.
Uma suíte de testes não precisa (e nem deve) depender apenas de um tipo de escrita, combine ambos e tenha uma suíte de testes elegante e funcional para a maioria dos casos de uso.