Skip to content

Instantly share code, notes, and snippets.

@geovannaotoni
Last active June 27, 2023 20:04
Show Gist options
  • Select an option

  • Save geovannaotoni/d3ef31950047625efe16ded2d3911d1d to your computer and use it in GitHub Desktop.

Select an option

Save geovannaotoni/d3ef31950047625efe16ded2d3911d1d to your computer and use it in GitHub Desktop.
Testing Library

React Testing Library

  1. Acessar os elementos na tela;
  2. Interagir com os elementos (se for necessário);
  3. Fazer os testes.
  • Renderização
import { render, screen } from '@testing-library/react';
import App from '../App';

render(<App />)

A função render() faz a renderização de um componente em memória para que se possa simular as interações com esse componente;

Funções da Biblioteca: Render, Screen, await WaitForElementToBeRemoved(() => screen.queryByText('...'))

  • Seletores:

Seletores ou Queries são métodos para indicar ao RTL algum elemento da aplicação.

Sintaxe: elemento = screen.seletor

Veja todos os seletores aqui https://testing-library.com/docs/queries/about/ e os roles de cada elemento aqui https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles e https://www.w3.org/TR/html-aria/#docconformance

Exemplos:

  • getByText(/texto/i),

  • getByRole('button', { name: Adicionar }), getByRole('img', { name: alt })

  • getAllByRole(),

  • getByLabelText(),

  • getByTestId(),

  • getByRole()

  • getByAltText(): serve para imagens

  • Teste Simples

Sintaxe: expect(element).matcher().

Exemplos:

  • toBe()

  • toHaveLength(2),

  • toHaveValue('Enviar'),

  • toHaveProperty('type', 'button')

  • toBeInTheDocument(): Verifica se o elemento está presente no documento.

  • toHaveTextContent(‘text’): Verifica se o elemento tem o conteúdo de texto especificado.

  • toHaveClass(className): Verifica se o elemento tem a classe especificada.

  • toBeDisabled(): Verifica se o elemento está desativado.

  • toBeEnabled(): Verifica se o elemento está habilitado.

  • toBeChecked(): Verifica se o elemento está marcado.

  • toBeEmpty(): Verifica se o elemento está vazio.

  • toBeVisible(): Verifica se o elemento é visível.

  • toBeHidden(): Verifica se o elemento está escondido.

  • toHaveAttribute(attribute, value): Verifica se o elem tem um atributo e valor específicos

  • Testando Eventos

A user-event é uma biblioteca complementar à React Testing Library (RTL) que possibilita a simulação de várias interações com o navegador (consegue disparar vários eventos para simular o comportamento de uma pessoa real usando a aplicação)

Importar a biblioteca para o arquivo onde o teste será realizado:

import userEvent from '@testing-library/user-event'

Principais Tipos de evento:

  • userEvent.type(element, text, [options]) : simula a digitação do usuário
  • userEvent.click(element): simula o evento de clique

Tipos de Queries

Queries No Match 1 Match 1+ Match Await?
getBy throw return throw No
findBy throw return throw Yes
queryBy null return throw No
getAllBy throw array array No
findAllBy throw array array Yes
queryAllBy [] array array No

Formas de utilizar o await: await waitFor(() => getByTestId()) ou await findByTestId()

Testando React Router

  • Adicione a chave no package.json: "overrides": { "@testing-library/dom": "^9.0.1"}
  • No terminal: npm i react-router-dom@v5
  • Crie a função helper:
// src/renderWithRouter.js
import React from 'react';
import { Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';
import { render } from '@testing-library/react';

const renderWithRouter = (component) => {
 const history = createMemoryHistory();
 return ({
   ...render(<Router history={ history }>{component}</Router>), history,
 });
};
export default renderWithRouter;

Para testar o history.push

Exemplo:

import React from 'react';
import { screen, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import renderWithRouter from '../renderWithRouter';
import App from '../App';

it('Teste se, ao tentar acessar uma rota inexistente: A aplicação é redirecionada para a página Not Found', () => {
    const { history } = renderWithRouter(<App />);
    const rotaNaoExistente = '/rota-nao-existente';
    
    //envolver com act
    act(() => {
      history.push(rotaNaoExistente);
    });
    
    const titleNotFound = screen.getByRole('heading', {
      name: /page not found/i,
    });
    expect(titleNotFound).toBeInTheDocument();
  });

Para testar o path de uma página

Exemplo:

it('Teste se, ao clicar no link About, a aplicação é redirecionada para a rota /about e o título About é renderizado na tela', () => {
    const { history } = renderWithRouter(<App />);
    const linkAbout = screen.getByRole('link', { name: /about/i, });
    
    userEvent.click(linkAbout);
    
    const { pathname } = history.location;
    expect(pathname).toBe('/about');

    // é preferível usar await screen.findByRole após um userEvent mas é possivel usar com screen.getByRole
    const titleAbout = screen.getByRole('heading', { name: /about/i, });
    expect(titleAbout).toBeInTheDocument();
  });

RTL - Mocks e Inputs

  • jest.fn(): transforma uma função em uma simulação, ele consegue mockar uma função randômica que está dentro de um módulo

Math.random = jest.fn().mockReturnValue() ou global.fetch = jest.fn().mockReturnValue() ou jest.fn().mockResolvedValue(resultadoDaPromisse)

  • jest.mock(): consegue mockar funções externas (módulos inteiros) - mocka todas as funcoes de um arquivo ao mesmo tempo

jest.mock(‘./nomeDoArquivo’)

(após a importação da função, o jest.mock deve ser declarado fora do describe, dentro do escopo do teste utiliza-se nomeDaFuncao.mockReturnValue() )

  • jest.spyOn(): espia a chamada da função simulada, mas deixa a implementação original ativa

jest.spyOn(global, 'fetch').mockReturnValue()

Mockando funções

  • Por ser uma simulação, podemos especificar qual vai ser o retorno da função através das propriedades mockReturnValue(value) ou mockReturnValueOnce(value).
  • Para criar um novo comportamento para a função simulada, utiliza-se o método mockImplementation(() => {})

Testar uma função que faz uma requisição para API:

  • Modo 1:
const mockRetorno = [{
   id: 1,
   name: 'Teste',
 }];

 jest.spyOn(global, 'fetch').mockResolvedValue({
   json: jest.fn().mockResolvedValue(mockRetorno),
 });
 render(<App />);
 const element = await screen.findByRole('heading', { name: 'Teste' });
 expect(element).toBeInTheDocument();
 expect(global.fetch).toBeCalledTimes(1);
  • Modo 2:
jest.spyOn(global, 'fetch'); 

global.fetch.mockResolvedValue({
   json: jest.fn().mockResolvedValue(joke),
 });
  • Modo 3:
jest.spyOn(global, 'fetch'); 

 global.fetch = jest.fn().mockResolvedValue({
   json: jest.fn().mockResolvedValue(joke),
 })
  • Modo 4:
jest.spyOn(global, 'fetch'); 

global.fetch = jest.fn(() => Promise.resolve({
   json: () => Promise.resolve(joke),
 }));
  • Modo 5:
jest.spyOn(global, 'fetch'); 

global.fetch = jest.fn(async () => ({
   json: async () => joke
 }));

Outras Configurações

  • mock.mockClear(): Útil quando você deseja limpar os dados de uso de uma simulação entre dois expects.
  • mock.mockReset(): Faz o que o mockClear() faz, Remove qualquer retorno estipulado ou implementação, Útil quando você deseja resetar uma simulação para seu estado inicial.
  • mock.mockRestore(): Faz tudo que mockReset() faz; Restaura a implementação original; Útil para quando você quer simular funções em certos casos de teste e restaurar a implementação original em outros.

Exemplos:

beforeEach(() => {
    global.fetch = jest.fn(mockFetch);
  });

  afterEach(() => {
    global.fetch.mockClear();
  });
  
  afterEach(() => jest.clearAllMocks());

Testes em React-Redux

Por conta da necessidade do uso do Provider em aplicações React com Redux, nos testes também precisamos utilizar o Provider para conseguirmos acessar a store.

  • Crie a função helper:
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { reducer } from './redux/reducers/reducer'

const renderWithRedux = (
  component,
  { 
    initialState,
    store = createStore(reducer, initialState) 
  } = {},
) => {
  return {
    ...render(<Provider store={store}>{component}</Provider>),
    store,
  };
};

Testando os valores da Store:

Utilizar o método getState() da store e acessar o valor da chave da store. Exemplo:

test('Incrementa o valor da store ao clicar no botão', async () => {
 const { store } = renderWithRedux(<App />);
 expect(screen.getByText('0')).toBeInTheDocument();
 const button = screen.getByText('Incrementa 1');
 userEvent.click(button);
 expect(await screen.findByText('1')).toBeInTheDocument();
 expect(store.getState().counterReducer.count).toBe(1);
});

Testes assíncronos:

Deve-se adicionar o thunk como terceiro argumento no createStore() da função renderWithRedux.

// src/helpers/renderWithRedux.js
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import { reducer } from './redux/reducers/reducer'

const renderWithRedux = (
  component,
  {
    initialState,
    store = createStore(
      reducer,
      initialState,
      applyMiddleware(thunk)
    ),
  } = {}
) => ({
  ...render(<Provider store={store}>{component}</Provider>),
  store,
});
export default renderWithRedux;

Testando aplicações com rotas e Redux:

Utilizar a função auxiliar renderWithRouterAndRedux.

import { Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';
import { render } from '@testing-library/react';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';

import { reducer } from './redux/reducers/reducer'

const renderWithRouterAndRedux = (
  component,
  {
    initialState = {},
    store = createStore(reducer, initialState, applyMiddleware(thunk)),
    initialEntries = ['/'],
    history = createMemoryHistory({ initialEntries }),
  } = {},
) => ({
  ...render(
    <Router history={ history }>
      <Provider store={ store }>
        {component}
      </Provider>
    </Router>,
  ),
  store,
  history,
});

Casos Exemplos:

  • Acessar diretamente uma rota específica:
test('Ao acessar diretamente a rota /profile, o nome da pessoa não pode ser renderizado', () => {
 const initialEntries = ['/profile'];

 renderWithRouterAndRedux(<App />, { initialEntries });

 expect(screen.queryByText('Boas vindas')).not.toBeInTheDocument();
 expect(screen.getByText('Você precisa realizar o login')).toBeInTheDocument();
});
  • Acessar diretamente uma rota específica alterando o estado global:
test('Ao acessar diretamente a rota /profile e alterando o estado global, o nome da pessoa deve ser renderizado', () => {
 const initialEntries = ['/profile'];
 const initialState = { userName: 'Tryber' };

 renderWithRouterAndRedux(<App />, { initialState, initialEntries });

 expect(
   screen.queryByText('Você precisa realizar o login')
 ).not.toBeInTheDocument();
 expect(screen.getByText('Boas vindas, Tryber')).toBeInTheDocument();
});

Testando aplicações com rotas e Provider:

import React from 'react';
import { Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';
import { render } from '@testing-library/react';
import MyProvider from '../../context/MyProvider';

const renderWithRouterAndContext = (component, path = '/') => {
  const history = createMemoryHistory({ initialEntries: [path] });
  return ({
    ...render(
      <Myrovider>
        <Router history={ history }>
          {component}
        </Router>
      </MyProvider>,
    ),
    history,
  });
};
export default renderWithRouterAndContext;

Estrutura Base para os testes

import React from 'react';
import { render, screen } from '@testing-library/react';
import App from '../App';
import seuMock from './helpers/seuMockAqui';
import SeuProvider from '../context/SeuProvider';
import userEvent from '@testing-library/user-event';

describe('Testes para o componente Table', () => {
beforeEach(() => {
    jest.spyOn(global, 'fetch');
    global.fetch = jest.fn().mockResolvedValue({
      json: jest.fn().mockResolvedValue(seuMockAqui),
    });

    render(
      <SeuProvider>
        <App />
      </SeuProvider>
    );
  });

  afterEach(() => {
    global.fetch.mockRestore();
  });

  it('', () => {
  // seu teste aqui
  })
}

Todos os direitos reservados à Trybe e às demais documentações citadas nos hyperlinks no arquivo, cujos materiais serviram de base para a construção deste gist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment