Skip to main content
Versão: 29.7

Testando Apps React

At Facebook, we use Jest to test React applications.

Instalação

Instalação com Create React App

If you are new to React, we recommend using Create React App. It is ready to use and ships with Jest! You will only need to add react-test-renderer for rendering snapshots.

Execute

npm install --save-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. We are using the babel-jest package and the react babel preset to transform our code inside of the test environment. Also see using babel.

Execute

npm install --save-dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer

Your package.json should look something like this (where <current-version> is the actual latest version number for the package). 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', {runtime: 'automatic'}],
],
};

And you're good to go!

Teste de Snapshot

Let's create a snapshot test for a Link component that renders hyperlinks:

Link.js
import {useState} from 'react';

const STATUS = {
HOVERED: 'hovered',
NORMAL: 'normal',
};

export default function 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>
);
}
note

Exemplos estão usando componentes de função, mas componentes de Classe podem ser testados da mesma forma. See React: Function and Class Components. Reminders that with Class components, we expect Jest to be used to test props and not methods directly.

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.test.js
import renderer from 'react-test-renderer';
import Link from '../Link';

it('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
renderer.act(() => {
tree.props.onMouseEnter();
});
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();

// manually trigger the callback
renderer.act(() => {
tree.props.onMouseLeave();
});
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();
});

When you run yarn test or jest, this will produce an output file like this:

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

exports[`changes the class when hovered 2`] = `
<a
className="hovered"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Facebook
</a>
`;

exports[`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. If the change is expected you can invoke Jest with jest -u to overwrite the existing snapshot.

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('../SomeDirectory/SomeComponent', () => 'SomeComponent');

Então você verá alertas no console:

Warning: <SomeComponent /> is using uppercase HTML. Always use lowercase HTML tags in React.

# Or:
Warning: The tag <SomeComponent> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.

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('./SomeComponent', () => () => 'SomeComponent');
  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.
    jest.mock('./Widget', () => () => <mock-widget />);
  3. Use react-test-renderer. The test renderer doesn't care about element types and will happily accept e.g. SomeComponent. 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

If you'd like to assert, and manipulate your rendered components you can use @testing-library/react, Enzyme, or React's TestUtils. The following example use @testing-library/react.

@testing-library/react

npm install --save-dev @testing-library/react

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

CheckboxWithLabel.js
import {useState} from 'react';

export default function 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>
);
}
__tests__/CheckboxWithLabel-test.js
import {cleanup, fireEvent, render} from '@testing-library/react';
import CheckboxWithLabel from '../CheckboxWithLabel';

// Note: running cleanup afterEach is done automatically for you in @testing-library/[email protected] 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.

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.

To make this work with Jest you need to update your Jest configuration with this: "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.