Skip to main content
Version: Next

Testando Apps React

No Facebook, usamos Jest para testar aplicativos React.

Instalação#

Instalação com Create React App#

Se você é novo no React, recomendamos que use Create React App. Está pronto para uso e já vem com Jest! Você só precisa adicionar o pacote react-test-renderer para renderizar snapshots.

Execute

yarn add --dev react-test-renderer

Instalação sem Create React App#

Se você tiver uma aplicação existente vai precisar instalar alguns pacotes para que tudo funcione bem junto. Estamos usando o pacote babel-jest e o preset react do Babel para transformar nosso código dentro do ambiente de teste. Consulte também usando Babel.

Execute

yarn add --dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer

Seu package.json deve parecer algo como isto (onde <current-version> é o número da versão mais recente para o pacote). Por favor, adicione as entradas scripts e de configuração jest:

"dependencies": {
"react": "<current-version>",
"react-dom": "<current-version>"
},
"devDependencies": {
"@babel/preset-env": "<current-version>",
"@babel/preset-react": "<current-version>",
"babel-jest": "<current-version>",
"jest": "<current-version>",
"react-test-renderer": "<current-version>"
},
"scripts": {
"test": "jest"
}
babel.config.js
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
};

E você está pronto para ir!

Teste de Snapshot#

Vamos criar um teste de snapshot para um componente chamado Link que renderiza hiperlinks:

Link.react.js
import React, {useState} from 'react';
const STATUS = {
HOVERED: 'hovered',
NORMAL: 'normal',
};
const Link = ({page, children}) => {
const [status, setStatus] = useState(STATUS.NORMAL);
const onMouseEnter = () => {
setStatus(STATUS.HOVERED);
};
const onMouseLeave = () => {
setStatus(STATUS.NORMAL);
};
return (
<a
className={status}
href={page || '#'}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{children}
</a>
);
};
export default Link;

Nota: Os exemplos estão utilizando componentes de Funções, mas os componentes de Classes podem ser testados da mesma maneira. Veja React: Componentes de Funções e Classes. Lembre-se que com componentes de Classes, nós esperamos utilizar o Jest para testar propriedades e não métodos diretamente.

Agora vamos usar o renderizador de teste do React e o recurso de snapshot do Jest para interagir com o componente e capturar a saída renderizada e criar um arquivo de snapshot:

Link.react.test.js
import React from 'react';
import renderer from 'react-test-renderer';
import Link from '../Link.react';
test('Link changes the class when hovered', () => {
const component = renderer.create(
<Link page="http://www.facebook.com">Facebook</Link>,
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
// manually trigger the callback
tree.props.onMouseEnter();
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();
// manually trigger the callback
tree.props.onMouseLeave();
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();
});

Ao executar yarn test ou jest, será produzido um arquivo de saída como a seguir:

__tests__/__snapshots__/Link.react.test.js.snap
exports[`Link changes the class when hovered 1`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}>
Facebook
</a>
`;
exports[`Link changes the class when hovered 2`] = `
<a
className="hovered"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}>
Facebook
</a>
`;
exports[`Link changes the class when hovered 3`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}>
Facebook
</a>
`;

Da próxima vez que você executar os testes, a saída renderizada será comparada ao snapshot criado anteriormente. O snapshot deve ser comitado junto às alterações de código. Quando um teste de snapshot falhar, é preciso inspecionar se trata-se de uma mudança pretendida ou não intencional. Se a mudança é esperada, pode-se invocar Jest com jest -u para substituir o snapshot existente.

The code for this example is available at examples/snapshot.

Teste de Snapshot com Mocks, Enzyme e React 16#

Existe uma ressalva em relação ao teste de snapshot quando se utiliza Enzyme e React 16+. Se você simular um módulo utilizando o seguinte estilo:

jest.mock('../AlgumDiretorio/AlgumComponente', () => 'AlgumComponente');

Então você verá alertas no console:

Alerta: <AlgumComponente/> está utilizando letras maiúsculas em HTML. Sempre utilize letras minúsculas em tags HTML no React.
# Ou: Alerta: A tag <AlgumComponente> não pode ser reconhecida neste navegador. Se você desejou renderizar um componente React, inicie seu nome com uma letra maiúscula.

React 16 emite estes alertas devido à forma que ele verifica tipos de elementos, e o módulo simulado falha nessas verificações. Suas opções são:

  1. Renderizar como texto. This way you won't see the props passed to the mock component in the snapshot, but it's straightforward: js jest.mock('./SomeComponent', () => () => 'SomeComponent');
    jest.mock('./AlgumComponente', () => () => 'AlgumComponente');
  2. Renderizar um elemento personalizado. "Elementos personalizados" da DOM não são verificados para nada e não devem emitir alertas. Eles são compostos de letras minúsculas e possuem um traço no nome.
    tsx
    jest.mock('./Widget', () =&#062; () =&#062; <mock-widget />);
  3. Utilize react-test-renderer. O renderizador de teste não se preocupa com o tipo de elemento e aceitará safisfatoriamente, por exemplo, AlgumComponente. Você pode verificar snapshots utilizando o renderizador de teste, e verificar o comportamento do componente separadamente utilizando o Enzyme.
  4. Desabilitar alertas todos juntos (deve ser feito no seu arquivo de configuração jest):
    jest.mock('fbjs/lib/warning', () => require('fbjs/lib/emptyFunction'));
    Isso não deveria ser sua opção de escolha dado que importantes alertas podem ser perdidos. Porém em alguns casos, por exemplo, quando testamos componentes react-native, nós renderizamos tags react-native no DOM e alertas que são irrelevantes. Outra opção é limpar o console.warn e suprimir alertas específicos.

Testando o DOM#

Se você gostaria de afirmar e manipular seus componentes renderizados, você pode utilizar react-testing-library, Enzyme, ou TestUtils do React. Os dois seguintes exemplos utilizam react-testing-library e Enzyme.

react-testing-library#

Você deve executar yarn add --dev @testing-library/react para utilizar o react-testing-library.

Vamos implementar uma caixa de seleção que alterna entre dois rótulos:

CheckboxWithLabel.js
import React, {useState} from 'react';
const CheckboxWithLabel = ({labelOn, labelOff}) => {
const [isChecked, setIsChecked] = useState(false);
const onChange = () => {
setIsChecked(!isChecked);
};
return (
<label>
<input type="checkbox" checked={isChecked} onChange={onChange} />
{isChecked ? labelOn : labelOff}
</label>
);
};
export default CheckboxWithLabel;
__tests__/CheckboxWithLabel-test.js
import React from 'react';
import {cleanup, fireEvent, render} from '@testing-library/react';
import CheckboxWithLabel from '../CheckboxWithLabel';
// Note: running cleanup afterEach is done automatically for you in @testing-library/react@9.0.0 or higher
// unmount and cleanup DOM after the test is finished.
afterEach(cleanup);
it('CheckboxWithLabel changes the text after click', () => {
const {queryByLabelText, getByLabelText} = render(
<CheckboxWithLabel labelOn="On" labelOff="Off" />,
);
expect(queryByLabelText(/off/i)).toBeTruthy();
fireEvent.click(getByLabelText(/off/i));
expect(queryByLabelText(/on/i)).toBeTruthy();
});

The code for this example is available at examples/react-testing-library.

Enzyme#

Você deve executar yarn add --dev enzyme para utilizar o Enzyme. Se você estiver utilizando uma versão do React menor que 15.5.0, também será necessário instalar react-addons-test-utils.

Vamos re-escrever o teste acima utilizando Enzyme ao invés do react-testing-library. Nós usamos o renderizador superficial do Enzyme neste exemplo.

__tests__/CheckboxWithLabel-test.js
import React from 'react';
import {shallow} from 'enzyme';
import CheckboxWithLabel from '../CheckboxWithLabel';
test('CheckboxWithLabel changes the text after click', () => {
// Render a checkbox with label in the document
const checkbox = shallow(<CheckboxWithLabel labelOn="On" labelOff="Off" />);
expect(checkbox.text()).toEqual('Off');
checkbox.find('input').simulate('change');
expect(checkbox.text()).toEqual('On');
});

The code for this example is available at examples/enzyme.

Transformadores personalizados#

If you need more advanced functionality, you can also build your own transformer. Instead of using babel-jest, here is an example of using @babel/core:

custom-transformer.js
'use strict';
const {transform} = require('@babel/core');
const jestPreset = require('babel-preset-jest');
module.exports = {
process(src, filename) {
const result = transform(src, {
filename,
presets: [jestPreset],
});
return result || src;
},
};

Don't forget to install the @babel/core and babel-preset-jest packages for this example to work.

Para fazer isto funcionar com Jest você precisa atualizar sua configuração Jest com isso: "transform": {"\\.js$": "path/to/custom-transformer.js"}.

If you'd like to build a transformer with babel support, you can also use babel-jest to compose one and pass in your custom configuration options:

const babelJest = require('babel-jest');
module.exports = babelJest.createTransformer({
presets: ['my-custom-preset'],
});

See dedicated docs for more details.