Muitas vezes, nos deparamos com classes de serviço ou comandos que possuem métodos privados complexos, responsáveis por transformações de dados e persistência. O dilema surge: devemos tornar o método público apenas para testar? Ou devemos testar apenas o método principal e esperar que tudo funcione?
Neste artigo, vamos explorar como testar métodos privados e garantir a integridade do banco de dados sem comprometer o encapsulamento da sua classe.
O Cenário Link para o cabeçalho
Imagine um sistema de logística que processa atualizações de rastreio vindas de uma fila. Temos um método privado saveDeliveryStatus que decide, com base em strings brutas, qual o status real da entrega.
// DeliveryService.php
private function saveDeliveryStatus(array $payload, array $response)
{
$status = $this->formatStatus($payload['raw_status']);
return Delivery::updateOrCreate(
['tracking_id' => $payload['id']],
['status' => $status, 'details' => $response['msg']]
);
}
O Desafio Link para o cabeçalho
1. Encapsulamento: O método é private. 2. Dependências de Estado: O método usa propriedades da classe (como uma instância de configuração carregada no construtor). 3. Persistência Dinâmica: Precisamos garantir que o updateOrCreate funcionou corretamente.
A Solução: Reflexão e Mocking de Estado Link para o cabeçalho
Para isolar o teste, utilizamos a classe ReflectionClass do PHP. Ela nos permite “abrir” a classe e injetar valores em propriedades privadas ou executar métodos protegidos.
1. Preparando o Ambiente de Banco
Em sistemas com migrations complexas ou quebradas, você pode definir a tabela manualmente no teste para garantir rapidez:
if (!Schema::connection('test_db')->hasTable('deliveries')) {
Schema::connection('test_db')->create('deliveries', function ($table) {
$table->id();
$table->string('tracking_id');
$table->string('status');
$table->timestamps();
});
}
2. Injetando Propriedades Privadas
Se o seu método depende de $this->config, você pode injetá-la sem precisar de um construtor complexo:
$service = new DeliveryService();
$reflection = new ReflectionClass($service);
$prop = $reflection->getProperty('config');
$prop->setAccessible(true);
$prop->setValue($service, (object)['priority' => 'high']);
3. Executando e Validando
Invocamos o método privado e usamos a lógica da própria classe para validar o resultado, evitando “hardcoding” de valores no teste:
$method = $reflection->getMethod('saveDeliveryStatus');
$method->setAccessible(true);
$method->invokeArgs($service, [$payload, $response]);
// Validando com a lógica de formatação da própria classe
$formatMethod = $reflection->getMethod('formatStatus');
$formatMethod->setAccessible(true);
$expectedStatus = $formatMethod->invoke($service, $payload['raw_status']);
$this->assertDatabaseHas('deliveries', [
'tracking_id' => 'TRK123',
'status' => $expectedStatus
], 'test_db');
Conclusão Link para o cabeçalho
Testar métodos privados não precisa ser um tabu. Ao usar Reflexão, você mantém seu código limpo e seguro, garantindo que as regras de negócio internas e a persistência de dados estejam funcionando exatamente como esperado.