Skip to main content
Version: Next

Testear Aplicaciones React

At Facebook, we use Jest to test React applications.

Setup

Setup con Create React App

Si eres nuevo en React, te recomendamos usar Create React App. Viene ya listo para usar e incluye Jest! Solo necesitarás añadir react-test-renderer para renderizar instantáneas.

Ejecutar

npm install --save-dev react-test-renderer

Setup sin Create React App

Si tiene alguna aplicación existente, necesitará instalar algunos paquetes para hacer que todo trabaje bien junto. Estamos usando el paquete babel-jest y react babel preset para transformar nuestro código dentro de el entorno de prueba. Recomendamos también vea using babel.

Ejecutar

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

Tu archivo package.json se debe ver como algo como ésto (donde <current-version> es la ultima versión del paquete). Por favor agregue los scripts y la configuración de 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'}],
],
};

Y así, ¡Estás listo para despegar!

Test con Instantánea

Vamos a crear un snapshot test para un componente Link que renderé un hyperlink:

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

Examples are using Function components, but Class components can be tested in the same way. See React: Function and Class Components. Reminders that with Class components, we expect Jest to be used to test props and not methods directly.

Ahora vamos usar las características render y snapshot de React y Jest para interactuar con los componentes y capturar lo que renderea, creando un archivo 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>
`;

La próxima vez que ejecutes la prueba, lo que se renderee será comparado con el snapshot previamente creado. Se debería aplicar el snapshot junto con los cambios de código. Cuando una prueba con snapshot falla, necesitará inspeccionar si se da por un cambio intencional o no intencional. Si el cambio es intencional, puede invocar Jest con jest -u para sobre escribir en snapshot existente.

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

Snapshot Testing with Mocks, Enzyme and React 16+

Hay una advertencia en torno a las pruebas de snapshot cuando se usa Enzyme y React 16+. Si simulas un módulo usando el estilo siguiente:

jest.mock('../SomeDirectory/SomeComponent', () => 'SomeComponent');

Entonces podrá ver advertencias en la consola:

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 dispara estos avisos debido a cómo comprueba los tipos de elementos y el módulo simulado falla esas comprobaciones. Tus opciones son:

  1. Renderizar como texto. De esta manera no verás los props enviados al componente simulado en el snapshot, pero es sencillo: js jest.mock('./SomeComponent', () => () => 'SomeComponent');
    jest.mock('./SomeComponent', () => () => 'SomeComponent');
  2. Render as a custom element. DOM "custom elements" aren't checked for anything and shouldn't fire warnings. They are lowercase and have a dash in the name.
    tsx
    jest.mock('./Widget', () =&#062; () =&#062; <mock-widget />);
  3. Use react-test-renderer. The test renderer doesn't care about element types and will happily accept e.g. SomeComponent. You could check snapshots using the test renderer, and check component behavior separately using Enzyme.
  4. Disable warnings all together (should be done in your jest setup file):
    jest.mock('fbjs/lib/warning', () => require('fbjs/lib/emptyFunction'));
    Disable warnings all together (should be done in your jest setup file): js jest.mock('fbjs/lib/warning', () => require('fbjs/lib/emptyFunction')); This shouldn't normally be your option of choice as useful warnings could be lost. However, in some cases, for example when testing react-native's components we are rendering react-native tags into the DOM and many warnings are irrelevant. Another option is to swizzle the console.warn and suppress specific warnings.

DOM Testing

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

Let's implement a checkbox which swaps between two labels:

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/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.

Transformers 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 que esto funcione con Jest necesita actualizar la configuración de Jest con ésto: "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.