Captura de argumentos em testes PHP

Fabio Hiroki - Oct 23 '21 - - Dev Community

Você já está se sentindo confiante com suas habilidades de teste e os seus colegas te invejam ao ouvir você falar de mocks e stubs. Mas agora, um novo desafio surge e você só pode verificar certas propriedades de um argumento, caso contrário seus testes falharão miseravelmente.

Gif mostrando alguém horrorizado

Um novo dia começa

Seu trabalho era implementar uma simples funcionalidade de cadastro de usuário. Como uma boa programadora, você planeja usar o padrão Repositório para poder separar a camada de persistência e mocká-la em testes unitários caso necessário.

Gif de alguém pensando que isso vai ser fácil

Classe usuário

Não poderia ser mais simples, certo?

final class User
{
    public function __construct(
        private string $id,
        private string $name)
    {
    }

    public function getId(): string
    {
        return $this->id;
    }

    public function getName(): string
    {
        return $this->name;
    }
}
Enter fullscreen mode Exit fullscreen mode

Repositório da entidade usuário

Não está persistindo de fato, mas vamos fingir que sim.

final class UserRepository
{
    public function save(User $user): void
    {
        echo sprintf('Persistindo usuário com id %s e nome %s', $user->getId(), $user->getName());
    }
}
Enter fullscreen mode Exit fullscreen mode

Classe UserService

Agora temos uma reviravolta! Um requisito maluco diz que o campo id deve ser randômico. Bom, temos várias maneiras de fazer isso, então vamos prosseguir:

final class UserService
{
    public function __construct(private UserRepository $repository) { }

    public function createNewUser(string $name): void
    {
        $user = new User(uniqid(), $name);

        $this->repository->save($user);
    }
}
Enter fullscreen mode Exit fullscreen mode

Legal, tudo está funcionando.

Não vamos nos esquecer dos testes

Gif de alguém lembrando de testar

Nós queremos verificar quando UserService cria um User, se o UserRepostory está sendo chamado corretamente. Não nos importamos com a persistência agora, então podemos mockar o repositório e tudo deve funcionar.

public function testFailing(): void
{
    $repository = $this->createMock(UserRepository::class);

    $userService = new UserService($repository);

    $repository
        ->expects($this->once())
        ->method('save')
        ->with(new User('id não randômico', 'user'));

    $userService->createNewUser('user');
}
Enter fullscreen mode Exit fullscreen mode

Infelizmente isso não funciona. O teste falha porque User com o id id não randômico não corresponde ao argumento passado pelo UserRepository que tem um id diferente cada vez que um novo usuário é criado.

Captura de argumento ao resgate!

No Java, nós temos essa mesa funcionalidade provida pelo Mockito:

Captura de argumento possibilita a verificação de apenas alguns valores do argumento ao invés de testar a equalidade do objeto inteiro.

No nosso exemplo, nós não nos precisamos testar realmente o valor do id porque ele é gerado randomicamente pelo próprio PHP através da função uniqid.

No final o teste usando captura de argumento fica assim

public function testRepositoryShouldCreateUserWithCorrectName(): void
{
    $repository = $this->createMock(UserRepository::class);

    $userService = new UserService($repository);

    $repository
        ->expects($this->once())
        ->method('save')
        ->will($this->returnCallback(function($user) {
            self::assertEquals('user', $user->getName());
        }));

    $userService->createNewUser('user');
}
Enter fullscreen mode Exit fullscreen mode

Obrigado por ler até aqui, espero que tenha gostado e boa sorte com seus testes!

Good luck!

. . . . . . . . . . . . . . . . . . . . .