# PHP: Pra que mockar?

Se você já escreve testes, em algum momento, deve ter recebido orientações para **mockar** uma classe com o objetivo de fazer um teste mais simples. Se você nunca passou por isso ou se ainda não entendeu o motivo de usar, esse artigo é pra você.

E antes de começar, quero destacar um fato importante: **mock** não é para ser usado em tudo. Em algumas situações, o que você vai precisar é de um **spy**, que é bem menos "agressivo" que um mock. Então eu não estou dizendo que você deve **mockar tudo em todos os testes**, por favor. Se seu teste pode ser escrito de forma simples e clara sem ele, escreva sem!

> Mas Kiko, o que é um mock?

O **mock** é um clone de alguma classe ou objeto, que serve para substituir a classe original de modo a te dar o controle sobre o que é esperado de acontecer durante o fluxo de testes.

Por exemplo, se você tem uma classe **Panela** que tem um método `cozinhar()`, em testes onde é preciso chamar esse método sem se importar com o que acontece dentro, você pode mockar a classe e criar a expectativa de que o método deve ser acionado pelo menos uma vez. Assim, se após a checagem isso não acontecer, uma falha será detectada.

Além desse cenário, o mock também serve para retirar dependências desnecessárias para o seu teste. Isso fica claro em situações onde uma classe tem acúmulo de responsabilidade e você quer testar somente uma delas. Para ignorar todas as outras, você vai mockar algumas classes chamadas ali dentro para criar retornos falsos, de modo que o teste não dependa do sucesso delas.

> Interessante... Mas como funciona um mock, Kiko?

Via **injeção de dependência**. Isso é uma estratégia que depende inteiramente de como o seu projeto foi desenvolvido. Por exemplo, se você usa um framework com gerenciamento de container como o Laravel, o mock pode ser atrelado diretamente a uma classe. Assim, quando você for instanciar essa classe usando o gerenciador, o Laravel instanciará o mock no lugar da classe.

Outra forma de injeção é quando você não instancia classe alguma no seu código, recebendo isso como entrada via construtor ou algum método em específico, que exemplifiquei no artigo anterior.

> Hum... Acho que entendi... Acho...

Então vamos ver na prática, continuando o exemplo da classe **Panela**:

```php
class Panela
{
    public function __construct(
        private array $ingredientes
    ) { }

    public function cozinhar(): void
    {
        foreach($this->ingredientes as $ingrediente) {
            $ingrediente->aumentarTemperatura();
        }
    }
}
```

Para complementar, farei uma classe **Ingrediente**:

```php
class Ingrediente
{
    protected float $congelado = 10.0;
    protected float $cru = 30.0;
    protected float $malCozido = 55.0;
    protected float $cozido = 65.0;
    protected float $bemCozido = 75.0;

    public function __construct(
        public readonly string $nome,
        protected float $temperatura = 25.0
    ) {
    }

    public function temperaturaAtual(): float
    {
        return $this->temperatura;
    }

    public function aumentarTemperatura(): void
    {
         $this->temperatura += 0.01;
    }

    public function estado(): string
    {
        if ($this->temperatura <= $this->congelado) {
            return "congelado";
        }

        if ($this->temperatura <= $this->cru) {
            return "cru";
        }

        if ($this->temperatura <= $this->malCozido) {
            return "mal cozido";
        }

        if ($this->temperatura <= $this->cozido) {
            return "cozido";
        }

        if ($this->temperatura <= $this->bemCozido) {
            return "bem cozido";
        }

        return "queimado";
    }
}
```

Nota: eu não manjo nada de cozinha, apenas chutei alguns valores.

> Ué, Kiko, como vai funcionar isso tudo?

Eu vou explorar um pouco de ***Design Patterns*** aqui. Cada ingrediente será uma classe que herda essa base de ingredientes, precisando configurar somente o nome do ingrediente e sua temperatura inicial (vindo via construtor). Já a temperatura de cada estado, é uma configuração opcional da hierarquia.

Também podemos fazer um *override* no método `aumentarTemperatura`, já que cada ingrediente tem um tempo de cozimento diferente.

Isso nos permitirá configurar ingredientes completamente diferentes. Por exemplo:

```php
class PeitoDeFrango extends Ingrediente
{ 
    public function __construct()
    {
        parent::__construct(
            nome: 'Peito de frango',
            temperatura: 20.0
        );
    }
}
```

```php
class CoxaoMole extends Ingrediente
{ 
    public function __construct()
    {
        parent::__construct(
            nome: 'Coxão Mole',
            temperatura: 28.0
        );
    }
}
```

Com isso, você pode ter a seguinte usabilidade:

```php
$coxaoMole = new CoxaoMole();
$panela = new Panela(ingredientes: [$coxaoMole]);

$repeticoes = 0;
while ($coxaoMole->temperaturaAtual() < 67.0) {
    $panela->cozinhar();
    $repeticoes++;
}

echo "Levou $repeticoes aumentos de temperatura para deixar o {$coxaoMole->nome} {$coxaoMole->estado()}!";
```

Brinque com o código: <https://paiza.io/projects/MmMfRYUKATVnyER0NLw5oQ?language=php>

Agora que montamos o nosso projetinho, vamos refletir sobre como podemos mockar alguma das classes citadas a fim de comprovar alguma coisa. Dada que a principal classe é a `Panela` e ela é a única que recebe outra em algum trecho do código, faz muito sentido que ela **não** seja mockada, somente suas dependências. No nosso experimento, ela só tem dependência com a classe **Ingrediente**.

E se você pensou dessa forma, você está plenamente certo(a). `Ingrediente` é o nosso alvo aqui. Se eu quero testar se a panela funciona, eu não preciso colocar um ingrediente de verdade, concorda? É só cozinhar qualquer coisa.

Nesse caso, nós vamos cozinhar um ingrediente falso. Para isso, nós precisamos criar essa classe falsa, por exemplo:

```php
class IngredienteMock extends Ingrediente
{
    public function __construct()
    {
        parent::__construct(
            name: "Mock",
            temperatura: -50.0
        );
    }
}
```

Essa é uma classe falsa, que tem como único objetivo validar interações entre os ingredientes e a panela. Digamos que eu queira comprovar que a classe panela aciona o método `aumentarTemperatura` do ingrediente... Há várias formas de se fazer isso.

```php
class IngredienteMock extends Ingrediente
{
    public bool $acionou = false;

    public function __construct()
    {
        parent::__construct(
            nome: "Mock",
            temperatura: -50.0
        );
    }

    public function aumentarTemperatura(): void
    {
        $this->acionou = true;
    }
}
```

E aí, no seu teste, você teria algo assim:

```php
$ingrediente = new IngredienteMock();
$panela = new Panela(ingredientes: [$ingrediente]);
$panela->cozinhar();
if ($ingrediente->acionou) {
    echo "Acionou o método aumentarTemperatura()";
} else {
    echo "Não acionou o método aumentarTemperatura()";
}
```

Veja essa brincadeira aqui: <https://paiza.io/projects/ppWVYgKRKjOxztCMzGIFPQ?language=php>

> Ah, então o Mock não necessariamente precisa fazer o que o código original faz, apenas **registrar os métodos acionados**, Kiko?

**Exatamente**. No geral, nós usamos o Mock para **simular coisas**. Então muitas vezes você vai ter um **Mock** que retorna algum resultado fictício para testar se esse resultado está influenciando no resto do código, etc.

A forma como montamos esse mock não é a ideal, na verdade, está bem longe! Afinal, nós temos diversas bibliotecas que nos fornecem várias classes auxiliadoras para clonar classes e tornar as verificações ainda mais assertivas.

Uma das bibliotecas mais usadas é a [Mockery.io](http://docs.mockery.io/en/latest/), que já vem integrada no PHPUnit e é bem simples de se usar. Por exemplo, aquele teste que fizemos poderia ser bem mais compacto:

```php

$ingrediente = Mockery::mock(Ingrediente::class);
$ingrediente->shouldReceive('aumentarTemperatura')->once();

$panela = new Panela(ingredientes: [$ingrediente]);
$panela->cozinhar();
```

Se o método não for chamado, durante o processo de desconstrução do objeto, o Mockery lançará um erro. Seu teste não terá nenhum problema se for chamado conforme esperado (somente uma vez).

## Conclusão

Embora o ato de **mockar** possa ser feito do zero, trabalhar com bibliotecas mais robustas e com um bom tempo de mercado pode ser uma escolha melhor. Até porque seu código fica mais legível e você perde menos tempo escrevendo os seus testes.

Outro detalhe é o fato de que **Mocks** estão aí para deixar seus testes mais focados, unitariamente falando. Se eu estou testando a panela, pouco importa o ingrediente, confere? Agora se eu estivesse testando o ingrediente, não teríamos o que mockar.

Se a classe que estiver mockando tiver algum método essencial para o teste (que faz parte do que está testando), não faça esse mock! No máximo, faça um **spy**, como mostra o [**Adam Wathan** nesse artigo sobre **Mockery mock vs spy**](https://adamwathan.me/2016/10/12/replacing-mocks-with-spies/). Não deixe brechas no seu teste!

E por hoje é só! Curtiu?? Comenta e compartilha! Acabei ficando até tarde escrevendo esse artigo só pra dar mais uma variada no blog. Prometo que no próximo eu voltarei com a série de PHP para Iniciantes, hahaha. Não deixa de acompanhar!

**Inté!**
