Teste automatizado de e-mail no Laravel
Thiago Alves • 26/06/2020
Confesso que ultimamente, ando bastante interessado em aprofundar-me sobre testes automatizados. No meu dia a dia, a preocupação com a cobertura de testes é cada vez maior.
Dias atrás, deparei-me com uma questão que quebrei a cabeça para testar. Eu precisava enviar um e-mail para um cliente e queria validar se a montagem dele acontecia da forma correta.
Na documentação do Laravel, encontrei uma opção chamada de Mail Fake, mas confesso que não atendeu a minha necessidade. Durante as minhas tentativas, fiz alterações no código que deveriam ter causado a "quebra" do teste, mas isso não aconteceu. Sendo assim, desisti de usá-lo.
Depois de conversar com um amigo, surgiu uma ideia interessante que me possibilitaria testar a classe Mailable
e
a view
, de uma forma bastante simples.
Abaixo, vou usar uma implementação fictícia para exemplificar o que fiz.
O cenário
Preciso enviar um e-mail para um cliente contendo o resumo do pedido de compra que ele fez no meu site.
Para isso, implementei uma classe Mailable
que recebe o ID
do pedido que enviarei na mensagem. No meu sistema, os
pedidos são representados classe pela Order
, que possui uma ligação direta com a classe User
, representando o
cliente que fez o pedido.
A view
do e-mail foi desenvolvida com Markdown, mas o mesmo poderia ser
feito com HTML.
Seguem abaixo, o código - ligeiramente resumido - do cenário descrito:
// app/User.php
class User extends Authenticatable
{
protected $fillable = [
'name',
'email',
'password',
];
}
// app/Order.php
class Order extends Model
{
protected $fillable = [
'total_price',
];
public function user()
{
return $this->belongsTo(User::class);
}
}
// app/Mail/OrderSummary.php
class OrderSummary extends Mailable
{
private $orderId;
public $order;
public function __construct($orderId)
{
$this->orderId = $orderId;
}
public function build()
{
return $this
->loadOrder()
->to($this->order->user->email, $this->order->user->name)
->markdown('emails.order-summary');
}
private function loadOrder()
{
$this->order = Order::find($this->orderId);
return $this;
}
}
// resources/views/emails/order-summary.blade.php
@component('mail::message')
# Olá, {{ $order->user->name }}
Seu pedido, no valor de R$ {{ $order->total_price }}, foi confirmado!
@component('mail::button', ['url' => 'thiagoalves.dev/pedido/' . $order->id])
Ver pedido
@endcomponent
@endcomponent
O teste
Como mencionado na introdução, o meu objetivo é testar a classe Mailable
e a montagem da view
, para me certificar de
que não existem erros nessa lógica, devido às mudanças que acontecerão no código ao longo do tempo.
Para isso, o primeiro passo é gerar dados falsos para usar no teste. Fiz isso usando as famosas factories
.
// database/factories/UserFactory.php
$factory->define(User::class, function (Faker $faker) {
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'password' => bcrypt('12345'),
];
});
// database/factories/OrderFactory.php
$factory->define(Order::class, function (Faker $faker) {
return [
'total_price' => $faker->numberBetween(100, 200),
'user_id' => function () {
return factory(User::class)->create()->getKey();
},
];
});
Para validar o que quero, vou usar dois testes apenas. Um que testará o método build do Mailable
e outro que valida a
renderização do corpo do e-mail. Segue abaixo.
// tests/Feature/Mail/OrderSummaryTest.php
class OrderSummaryTest extends TestCase
{
private $mail;
protected function setUp(): void
{
parent::setUp();
$order = factory(Order::class)->create();
$this->mail = new OrderSummary($order->getKey());
}
public function testBuildSuccess()
{
$this->assertInstanceOf(OrderSummary::class, $this->mail->build());
}
public function testRenderSuccess()
{
$this->assertIsString($this->mail->render());
}
}
Simples, né? Apenas isso é o suficiente para verificar se toda a lógica de montagem do e-mail acontece corretamente. Ambos validam o retorno de métodos que devem falhar se algo de errado acontecer.
Veja o resultado:
Agora se eu remover o relacionamento do pedido com o usuário, por exemplo, as duas verificações devem quebrar.
// app/Order.php
class Order extends Model
{
protected $fillable = [
'total_price',
];
}
Resultado:
Este teste não inclui validar o envio do e-mail, uma vez que isso geralmente depende de um servidor externo. O foco é
realmente testar a sua montagem, não uma integração com o servidor SMTP
.
Concluindo
Envio de e-mails tende a ser uma das partes mais obscuras de um sistema e também uma das mais chatas de se testar.
Já perdi a conta de quantas vezes fiz modificações num código, afetando o envio de um e-mail que, muitas vezes, nem tinha relação direta com o que foi alterado.
O código acima está disponível no meu repositório do github para que possa copiar. Espero que ajude.
Nos vemos em breve!
Thiago Alves
Comente abaixo o que você achou deste post, se ficou com alguma dúvida ou se gostaria de sugerir algum assunto.