Mocks agnósticos para bibliotecas de requisições HTTP no Node.js
Leonardo Giraldi Moreno Giuranno
Posted on October 23, 2023
Desde que comecei a implementar testes unitários para os projetos e aplicações que estava envolvido, configurava o mock das bibliotecas de requisições HTTP individualmente. Por exemplo, se eu utilizasse o axios
para fazer chamadas a outras APIs, durante a implementação dos testes, eu configurava o mock do axios
em si. Vamos dar uma olhadinha nessa implementação:
Considere a seguinte função:
const axios = require("axios");
async function fetchCep(cep) {
const { data } = await axios.get(`https://viacep.com.br/ws/${cep}/json/`);
return data;
}
module.exports = {
fetchCep,
};
A simples função fetchCep(cep)
utiliza a biblioteca axios
para fazer uma solicitação HTTP GET para um serviço de consulta de CEP (Código de Endereçamento Postal) fornecido pela API do ViaCEP. O objetivo desta função é obter informações de endereço com base em um CEP fornecido como argumento.
Agora vamos implementar o teste unitário que valida se a função fetchCep
retorna as informações do CEP fornecido obtidas na API do ViaCEP:
const mockAxiosGet = jest.fn();
jest.mock("axios", () => ({
get: mockAxiosGet,
}));
const { fetchCep } = require("./axiosMockExample");
describe("fetchCep", () => {
it("should return cep information", async () => {
mockAxiosGet.mockResolvedValueOnce({
data: {
cep: "12345678",
logradouro: "Avenida Paulista",
bairro: "Centro",
localidade: "São Paulo",
uf: "SP",
},
});
const cep = "12345678";
const result = await fetchCep(cep);
expect(result.cep).toBe("12345678");
expect(result.logradouro).toBe("Avenida Paulista");
expect(result.bairro).toBe("Centro");
expect(result.localidade).toBe("São Paulo");
expect(result.uf).toBe("SP");
});
});
Em um primeiro momento, estamos configurando o mock da biblioteca axios
e de sua função get
:
const mockAxiosGet = jest.fn();
jest.mock("axios", () => ({
get: mockAxiosGet,
}));
Logo em seguida, realizamos a importação da nossa função fetchCep
:
const { fetchCep } = require("./axiosMockExample");
O primeiro passo do teste “should return cep information" é configurar o retorno do método get
da biblioteca axios
:
mockAxiosGet.mockResolvedValueOnce({
data: {
cep: "12345678",
logradouro: "Avenida Paulista",
bairro: "Centro",
localidade: "São Paulo",
uf: "SP",
},
});
Configurado o retorno do método get
, realizamos a chamada à nossa função fetchCep
e validamos se obtivemos o retorno esperado:
const cep = "12345678";
const result = await fetchCep(cep);
expect(result.cep).toBe("12345678");
expect(result.logradouro).toBe("Avenida Paulista");
expect(result.bairro).toBe("Centro");
expect(result.localidade).toBe("São Paulo");
expect(result.uf).toBe("SP");
Agora, basta executarmos nosso teste utilizando o jest:
Excelente! Nosso teste passou como esperado! 🥳
Porém, e se, por algum motivo, necessitamos alterar a biblioteca de requisições HTTP que estamos utilizando atualmente, o axios
? O que aconteceria com nosso teste implementado? Vamos conferir?
Suponhamos que a biblioteca axios
foi trocada pela Fetch API nativa do Node.js:
async function fetchCep(cep) {
const response = await fetch(`https://viacep.com.br/ws/${cep}/json/`);
return response.json();
}
module.exports = {
fetchCep,
};
Vamos agora executar nosso teste unitário:
Como podemos ver, nosso teste passou a falhar. O motivo da falha está relacionado ao fato de termos configurado um mock completamente preso à biblioteca que utilizávamos anteriormente, o axios
.
Para solucionarmos essa dependência utilizaremos a biblioteca [Mock Service Worker (MSW)](https://mswjs.io/). Essa biblioteca é uma ferramenta que permite simular e interceptar solicitações de rede em aplicativos web e Node.js durante os testes. Ela fornece uma maneira conveniente de criar mocks para as respostas de chamadas de rede, permitindo que os desenvolvedores controlem o comportamento das solicitações HTTP feitas pelo aplicativo em testes unitários, sem realmente realizar solicitações à rede real. Isso é especialmente útil para isolar testes e garantir que o aplicativo funcione corretamente, mesmo quando a API ou os serviços externos não estão disponíveis, enquanto também simplifica a simulação de diferentes cenários de respostas de rede para fins de teste.
Agora, vamos configurar o service worker em nosso arquivo de testes. Para isso, precisamos instalar o pacote do MSW em nossas dependências de desenvolvimento utilizando o npm ou yarn:
npm install msw --save-dev
No arquivo de testes, vamos importar o objeto rest
do pacote msw
e a função setupServer
do pacote msw/node
:
const { setupServer } = require("msw/node");
const { rest } = require("msw");
Antes da declaração da nossa suite de testes, o describe
, vamos configurar nosso server, o qual será responsável por interceptar todas as chamas realizadas por qualquer biblioteca de requisições HTTP:
const server = setupServer(
rest.get("https://viacep.com.br/ws/:cep/json/", (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
cep: "12345678",
logradouro: "Avenida Paulista",
bairro: "Centro",
localidade: "São Paulo",
uf: "SP",
})
);
})
);
Como podemos ver, estamos configurando o comportamento do interceptor para requisições HTTP do tipo GET
no endpoint https://viacep.com.br/ws/:cep/json/
. Note que em :cep
, estamos abstraindo qualquer CEP que seja fornecido durante a requisição HTTP. A configuração realizada retornará o status code 200
e o objeto contendo as informações do CEP no corpo da resposta.
Para mais informações sobre possibilidades de configuração de interceptores, recomendo consultarem a documentação oficial do MSW.
Estamos quase lá! Precisamos, das seguintes configurações:
- Antes de todos os testes da suite, inicializar nosso servidor:
beforeAll(() => server.listen());
- Após cada teste da suite, restaurar a configuração original do nosso servidor, definida acima. Essa restauração garante que, caso adicionemos novos handlers ou sobrescrevemos os existentes dentro dos testes, a configuração do servidor seja a original antes de cada teste ser executado dentro de nossa suite:
afterEach(() => server.resetHandlers());
- Após todos os testes da suite, encerrar nosso servidor:
afterAll(() => server.close());
Os últimos dois pontos a serem alterados em nosso arquivo de testes é a remoção do mock da biblioteca axios
e da configuração do retorno do seu método get
:
// REMOVER
const mockAxiosGet = jest.fn();
jest.mock("axios", () => ({
get: mockAxiosGet,
}));
...
// REMOVER
mockAxiosGet.mockResolvedValueOnce({
data: {
cep: "12345678",
logradouro: "Avenida Paulista",
bairro: "Centro",
localidade: "São Paulo",
uf: "SP",
},
});
Finalmente, a nossa implementação do teste ficará assim:
const { setupServer } = require("msw/node");
const { rest } = require("msw");
const { fetchCep } = require("./axiosMockExample");
const server = setupServer(
rest.get("https://viacep.com.br/ws/:cep/json/", (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
cep: "12345678",
logradouro: "Avenida Paulista",
bairro: "Centro",
localidade: "São Paulo",
uf: "SP",
})
);
})
);
describe("fetchCep", () => {
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
it("should return cep information", async () => {
const cep = "12345678";
const result = await fetchCep(cep);
expect(result.cep).toBe("12345678");
expect(result.logradouro).toBe("Avenida Paulista");
expect(result.bairro).toBe("Centro");
expect(result.localidade).toBe("São Paulo");
expect(result.uf).toBe("SP");
});
});
Agora, temos nosso teste passando com sucesso:
Neste exemplo, nos baseamos em uma requisição HTTP do tipo GET
. Porém, a biblioteca MSW permite configurarmos interceptores para todos os verbos HTTP.
Recomendo fortemente o estudo da documentação oficial da biblioteca para implementação dos testes de seus projetos e aplicações.
Posted on October 23, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.