Mocks vs Stubs 🤼
Published: 2024-04-06 19:35:24

Mocks são ferramentas úteis para **simular comportamentos** de partes do seu código que **dependem** de sistemas **externos** ou complexos, permitindo testar seu código de forma **isolada** e eficiente. Usar mocks ajuda a manter seus testes de unidade rápidos e focados apenas no que você quer testar, sem depender de serviços de rede, bancos de dados ou qualquer outro recurso externo.

---

## Exemplo 📖

Uma classe para pesquisa de livros, que interage com um serviço externo, neste cenário, temos uma função `getBook` que busca dados de um livro por meio de uma API externa.

```
// BookSearch.js
class BookSearch {
  constructor(bookService) {
    this.bookService = bookService;
  }

  async getBook(isbn) {
    const json = await this.bookService.search(isbn);
    const data = JSON.parse(json);
    return { titulo: data.titulo };
  }
}

module.exports = BookSearch;

```

---

## Problema 🚧
Para testar `BookSearch` isoladamente, precisamos de um `bookService`, que é um **serviço externo**. Testar diretamente com o serviço real tornaria o teste lento e instável, pois depende de uma rede e de um serviço que pode não estar sempre disponível ou responder de forma consistente.

---

## Solução 🌟 
Para resolver esse problema, podemos criar um mock para `BookService`. Em JavaScript, isso pode ser feito facilmente com o auxílio de bibliotecas de testes como Jest, que oferecem suporte embutido para criar mocks.

```
// MockBookService.js
class MockBookService {
  async search(isbn) {
    if (isbn === 1234) {
      return JSON.stringify({ titulo: "Eng Soft Moderna" });
    }
    return JSON.stringify({ titulo: "NULL" });
  }
}

module.exports = MockBookService;

```

E o **teste** poderia ser escrito assim:

```
// BookSearch.test.js
const BookSearch = require('./BookSearch');
const MockBookService = require('./MockBookService');

describe('BookSearch', () => {
  test('getBook returns correct title', async () => {
    const service = new MockBookService();
    const bookSearch = new BookSearch(service);
    const book = await bookSearch.getBook(1234);
    expect(book.titulo).toBe('Eng Soft Moderna');
  });
});
```

---

Neste cenário, o mock `MockBookService` simula a resposta de uma API externa sem realmente fazer uma chamada de rede. Isso mantém o teste rápido e independente do ambiente externo. Embora não estejamos testando a interação real com o serviço externo, o mock nos permite verificar se `BookSearch` está processando corretamente o JSON retornado e criando o objeto Book como esperado. Assim, podemos focar no teste da lógica interna de `BookSearch`, garantindo que ela funciona corretamente, independentemente da confiabilidade ou disponibilidade do serviço externo.

---

## Mocks vs Stubs 🤼
- **Mocks**: São objetos que **simulam** o comportamento de objetos reais de maneira controlada. Em testes de unidade, os mocks são usados para **verificar** interações entre partes do código, ou seja, eles podem ser utilizados para garantir que certas funções foram chamadas, com quais parâmetros foram chamadas, e quantas vezes. Isso está relacionado ao teste do comportamento do código.

- **Stubs**: São simplificações de objetos que substituem funcionalidades que não são o foco do teste. Eles oferecem respostas predefinidas a chamadas específicas. Diferente dos mocks, os stubs **não** são usados para verificar interações ou comportamentos. Eles são mais sobre **fornecer** um ambiente de teste controlado ao substituir partes do sistema que estão fora do escopo do teste atual.

---

Em JavaScript, usando Jest como exemplo, você pode criar tanto mocks quanto stubs de maneira simples.

## Exemplo 📖
Vamos considerar um exemplo simples onde temos uma função de negócio que depende de um serviço de envio de emails. Usaremos Jest para demonstrar tanto o conceito de mock (verificando o comportamento) quanto de stub (substituindo uma dependência complexa por uma versão simplificada).

## Criando um Mock 🎭
Supondo que temos uma classe `Mailer` que queremos mockar:

```
class Mailer {
  send(email) {
    // Lógica de envio de email
  }
}

```

E um **SUT** que usa Mailer:

```

function someBusinessLogic(mailer, message) {
  mailer.send(message);
}
```

Podemos usar Jest para mockar `Mailer` e verificar se send foi chamado:

```

const mailer = new Mailer();
jest.mock('./Mailer');

test('verifies Mailer.send is called', () => {
  someBusinessLogic(mailer, "Hello");
  expect(mailer.send).toHaveBeenCalledWith("Hello");
});


```

## Criando um Stub 📌
Se, ao contrário, só precisássemos de um `Mailer `simplificado que não fizesse nada (apenas satisfazendo o tipo esperado sem influenciar o teste), poderíamos criar um stub assim:

```
const mailerStub = {
  send: jest.fn() // Isso cria um stub que satisfaz a interface mas não faz nada
};
```
Embora a distinção entre **mocks** e **stubs** possa ser sutil, entender quando usar cada um pode ajudar a escrever testes mais claros e focados. Em JavaScript, com ferramentas como Jest, criar e usar mocks e stubs é bastante **direto**, facilitando a implementação de testes de unidade eficazes que podem verificar comportamentos específicos (mocks) ou apenas substituir partes do sistema para isolamento (stubs).

---

## Exemplo: Criando API em JavaScript 🌐

Primeiro, temos um simples endpoint que calcula o IMC:

```

// IMCController.js
const express = require('express');
const app = express();
app.use(express.json());

const IMCModel = {
  calculaIMC(peso, altura) {
    return peso / (altura * altura);
  }
};

app.get('/imc', (req, res) => {
  const { peso, altura } = req.query;
  const imc = IMCModel.calculaIMC(parseFloat(peso), parseFloat(altura));
  res.send({ imc: `IMC: ${imc}` });
});

module.exports = app; // Para permitir importação no teste

```

Usando Jest, podemos **testar** essa API sem precisar fazer chamadas reais. Jest nos permite mockar partes do Express para simular requisições e respostas.

```
// IMCController.test.js
const request = require('supertest');
const app = require('./IMCController');

describe('GET /imc', () => {
  it('calculates IMC correctly', async () => {
    const response = await request(app).get('/imc?peso=82&altura=1.80');
    expect(response.text).toEqual("{\"imc\":\"IMC: 25.30864197530864\"}");
  });
});

```

No exemplo acima, **não** precisamos de mocks para req e res porque o supertest cuida disso, permitindo que façamos chamadas ao nosso app Express como se fossem chamadas reais, mas tudo é simulado e nada vai para uma rede real.

Em JavaScript, o uso de mocks e stubs é simplificado por bibliotecas como Jest, que permitem a você **focar** na lógica dos testes sem se preocupar com a complexidade de criar e gerenciar esses objetos dublês. 

No entanto, **cuidado:** o uso de mocks pode aumentar o acoplamento entre os testes e a implementação, tornando os testes frágeis a mudanças internas. Portanto, é importante usar mocks com cautela e sempre ponderar sobre a melhor abordagem para cada situação de teste.

---

A maior parte desse conteúdo veio do livro "Engenharia de Software Moderna", escrito por Marco Tulio Valente. Esse livro é um tesouro cheio de dicas sobre como criar testes do jeito certo, usando o que tem de mais novo e eficiente no mundo da programação. Entretanto, a única diferença, é que o livro é em Java e aqui eu adaptei para utilizar Javascript.

---