Skip to main content
Version: 27.0

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

yarn add --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

yarn add --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'],
};

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

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.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();
});

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

// __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>
`;

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.

Pruebas de Snapshot con Simulaciones, Enzyme y 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 react-testing-library, Enzyme, or React's TestUtils. The following two examples use react-testing-library and Enzyme.

react-testing-library#

You have to run yarn add --dev @testing-library/react to use react-testing-library.

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

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

You have to run yarn add --dev enzyme to use Enzyme. If you are using a React version below 15.5.0, you will also need to install react-addons-test-utils.

Let's rewrite the test from above using Enzyme instead of react-testing-library. Usaremos Enzyme shallow renderer en este ejemplo.

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

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.