Mocks vs Stubs 🤼

Rmag Breaking News

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.

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 Mailersimplificado 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.

Leave a Reply

Your email address will not be published. Required fields are marked *