Mocks agnósticos para bibliotecas de requisições HTTP no Node.js

giraldidev

Leonardo Giraldi Moreno Giuranno

Posted on October 23, 2023

Mocks agnósticos para bibliotecas de requisições HTTP no Node.js

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,
};
Enter fullscreen mode Exit fullscreen mode

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");
    });
});
Enter fullscreen mode Exit fullscreen mode

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,
}));

Enter fullscreen mode Exit fullscreen mode

Logo em seguida, realizamos a importação da nossa função fetchCep :

const { fetchCep } = require("./axiosMockExample");
Enter fullscreen mode Exit fullscreen mode

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",
    },
});
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

Agora, basta executarmos nosso teste utilizando o jest:

Resultado de sucesso na execução dos testes

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,
};
Enter fullscreen mode Exit fullscreen mode

Vamos agora executar nosso teste unitário:

Resultado de falha na execução dos testes

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
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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",
            })
        );
    })
);
Enter fullscreen mode Exit fullscreen mode

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());
Enter fullscreen mode Exit fullscreen mode
  • 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());
Enter fullscreen mode Exit fullscreen mode
  • Após todos os testes da suite, encerrar nosso servidor:
afterAll(() => server.close());
Enter fullscreen mode Exit fullscreen mode

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",
    },
});
Enter fullscreen mode Exit fullscreen mode

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");
    });
});
Enter fullscreen mode Exit fullscreen mode

Agora, temos nosso teste passando com sucesso:

Resultado de sucesso na execução dos testes

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.

💖 💪 🙅 🚩
giraldidev
Leonardo Giraldi Moreno Giuranno

Posted on October 23, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related