- Acessar os elementos na tela;
- Interagir com os elementos (se for necessário);
- 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)oumockReturnValueOnce(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.