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.