React Testing Library: Mockando Axios Get individualmente por teste

  • Publicado em: 15/02/2022
  • Atualizado em: 12/05/2022

OLAR, bora aprender como mockar o método Axios.get do Next?

Mais um post sobre Mock porque a vida não está fácil.
Chega de soluções mirabolantes para mockar um simples método de lib.

Libs e versões utilizadas

  • “next”: “^9.4.4”;
  • “react”: “^16.13.1”;
  • “@testing-library/jest-dom”: “^5.10.1”;
  • “@testing-library/react”: “^10.2.1”;

O componente

Dando um pouco de contexto, meu componente tem um botão que executa a função redirectToAnotherPage, onde vai setar um cookie (utilizando a lib universal-cookie e isso é irrelevante 😂) e depois dar um Router.push, redirecionando o usuário. Essa função está fora do componente simplesmente pra não sofrer nenhum rerender.

import React from 'react'; import { Router } from 'next-router'; import Cookies from 'universal-cookie'; const redirectToAnotherPage = () => { const cookies = new Cookies(); cookies.set('cookie_imaginario', 'true', { path: '/' }); Router.push('/another-page'); }; const MyComponent = () => { return ( <button type="button" onClick={redirectToAnotherPage}>Redirecionar</button> ) }; export default MyComponent;

Rodando o comando do Jest para coletar a cobertura de código jest --collect-coverage, recebi o resultado abaixo. Tudo em vermelho significa statement not covered.

Resultado de cobertura de teste monstrando que os métodos cookies.set e Router.push não estão cobertos nos testes

Desenvolvendo o pensamento

Antes de tudo, vamos testar se nosso botão está no DOM:

import React from 'react'; import { render, screen } from '@testing-library/react'; import MyComponent from './index'; describe('[Components]: Button', () => { test('should be in the DOM', () => { render(<MyComponent />); const button = screen.getByText('Redirecionar'); expect(button).toBeInTheDocument(); }); });

Agora vamos criar outro teste somente clicando no botão ( fireEvent dentro de act) e sem expect:

import React from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; import { act } from 'react-dom/test-utils'; import MyComponent from './index'; describe('[Components]: Button', () => { test('should be in the DOM', () => { render(<MyComponent />); const button = screen.getByText('Redirecionar'); expect(button).toBeInTheDocument(); }); test('should redirect', () => { render(<MyComponent />); const skipButton = screen.getByText('Redirecionar'); act(() => { fireEvent.click(skipButton); }); }); });

Este teste passou, é claro, não espera nada. Agora temos que pensar em como interceptar o método executado após o click. Para isso precisaremos de um spyOn do Jest. A sintaxe segundo a documentação é: jest.spyOn(objeto, método), então vamos importar o Router e testar:

import React from 'react'; import { Router } from 'next-router'; // ... outros imports test('should redirect', () => { const spyRouter = jest.spyOn(Router, 'push'); render(<MyComponent />); const skipButton = screen.getByText('Redirecionar'); act(() => { fireEvent.click(skipButton); }); expect(spyRouter).toHaveBeenCalled(); });

Ué, deu erro… Está dizendo que nenhuma instância do router foi encontrada e que nós deveríamos usar next/router somente dentro do client side da nossa aplicação… 🤔

No router instance found. You should only use "next/router" inside the client side of your app. 6 | const cookies = new Cookies(); 7 | cookies.set('cookie_imaginario', 'true', { path: '/' }); > 8 | Router.push('/another-page'); | ^ 9 | };

A questão é que existe uma instância sim, ela só não está mockada. E como nós provamos isso? Basta adicionar um console.log(Router), logo após abrir o teste should redirect e, no seu terminal poderá ver a resposta abaixo:

console.log { router: null, readyCallbacks: [ [Function], [Function], [Function], [Function], [Function], [Function] ], ready: [Function: ready], push: [Function], replace: [Function], reload: [Function], back: [Function], prefetch: [Function], beforePopState: [Function], pushRoute: [Function], replaceRoute: [Function], prefetchRoute: [Function] }

Vamos deixar o console.log onde está, adicionar uma linha no nosso código após os imports e rodar o teste novamente:

import React from 'react'; import { Router } from 'next-router'; // ... outros imports jest.mock('next/router');

Agora os métodos foram mockados, o teste passou e a cobertura está com 100% neste componente. 🎉 🎉

E como eu sei que não é um falso positivo? Simples, coloque expect(true).toBe(false); logo após o expect(spyRouter).toHaveBeenCalled(); e veja o teste quebrar. 😊

Não vou colar a resposta inteira do console.log mas abaixo podemos ver como ficou o método push. Veja todos os mock-alguma-coisa indicando que o mock está funcionando:

push: [Function: mockConstructor] { _isMockFunction: true, getMockImplementation: [Function], mock: [Getter/Setter], mockClear: [Function], mockReset: [Function], mockRestore: [Function], mockReturnValueOnce: [Function], mockResolvedValueOnce: [Function], mockRejectedValueOnce: [Function], mockReturnValue: [Function], mockResolvedValue: [Function], mockRejectedValue: [Function], mockImplementationOnce: [Function], mockImplementation: [Function], mockReturnThis: [Function], mockName: [Function], getMockName: [Function] },

Solução final

import React from 'react'; import { Router } from 'next-router'; import { fireEvent, render, screen } from '@testing-library/react'; import { act } from 'react-dom/test-utils'; import MyComponent from './index'; jest.mock('next/router'); describe('[Components]: Button', () => { test('should be in the DOM', () => { render(<MyComponent />); const button = screen.getByText('Redirecionar'); expect(button).toBeInTheDocument(); }); test('should redirect', () => { const spyRouter = jest.spyOn(Router, 'push'); render(<MyComponent />); const skipButton = screen.getByText('Redirecionar'); act(() => { fireEvent.click(skipButton); }); expect(spyRouter).toHaveBeenCalled(); }); });

Solução alternativa

Na primeira solução nós estávamos importando o next/router e desestruturando o Router, agora nós estamos lidando com todos os métodos da lib por isso o push está dentro da chave default. Lembrando que para ver o objeto e seus métodos basta fazer: console.log(require('next/router')).

import React from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; import { act } from 'react-dom/test-utils'; import MyComponent from './index'; jest.mock('next/router', () => ({ ...jest.requireActual('next/router'), default: { push: jest.fn(), }, })); describe('[Components]: Button', () => { test('should be in the DOM', () => { render(<MyComponent />); const button = screen.getByText('Redirecionar'); expect(button).toBeInTheDocument(); }); test('should create cookie and redirect', () => { const spyRouter = jest.spyOn(require('next/router').default, 'push'); render(<MyComponent />); const skipButton = screen.getByText('Redirecionar'); act(() => { fireEvent.click(skipButton); }); expect(spyRouter).toHaveBeenCalled(); }); });

Referências

Felipe Marciano
Sobre o autor
Felipe Marciano

dev front-end, viciado em café, cinema e abraçar cachorros.

Tem perguntas,fale comigo.Respondo assim que puder.
Para mandar um "Oi" 👋felipexperto@gmail.com
Me siga no LinkedIn 🚀linkedin.com/in/felipexperto/
© 2024, Construído com Gatsby