Mocks agnósticos para bibliotecas de requisições HTTP no Node.js
Tags: node, testing, javascript
Published: 2023-10-23 11:30:00

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:

```jsx
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](https://viacep.com.br/). 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:

```jsx
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`:

```jsx
const mockAxiosGet = jest.fn();
jest.mock("axios", () => ({
	  get: mockAxiosGet,
}));

```

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

```jsx
const { fetchCep } = require("./axiosMockExample");
```

O primeiro passo do teste “*should return cep information*" é configurar o retorno do método `get` da biblioteca `axios`:

```jsx
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:

```jsx
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:

![Resultado de sucesso na execução dos testes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9fm19e4y7wn1xf5qggsu.png)

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](https://www.notion.so/Mocks-agn-sticos-para-bibliotecas-de-requisi-es-HTTP-no-Node-js-ae51be87c5a840c3ac8d913134de2c52?pvs=21):

```jsx
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:

![Resultado de falha na execução dos testes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/80bw39c6tbk0uru6f8n4.png)

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*:

```bash
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`:

```jsx
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:

```jsx
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](https://mswjs.io/docs/).

Estamos quase lá! Precisamos, das seguintes configurações:

- Antes de todos os testes da *suite*, inicializar nosso servidor:

```jsx
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:*

```jsx
afterEach(() => server.resetHandlers());
```

- Após todos os testes da *suite*, encerrar nosso servidor:

```jsx
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`:

```jsx
// 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:

```jsx
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:

![Resultado de sucesso na execução dos testes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mhodw83b8w6091wshnst.png)

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.