Testing React Apps
En Facebook, usamos Jest para probar aplicaciones React.
Setup #
Setup con Create React App #
Si usted esta iniciando con React, recomendamos use Create React App. Si esta listo para usar zarpemos con Jest! No necesita hacer pasos extras para el setup, así que podemos pasar directamente a la siguiente sección.
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-es2015 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:
// package.json "dependencies": { "react": "<current-version>", "react-dom": "<current-version>" }, "devDependencies": { "babel-jest": "<current-version>", "babel-preset-es2015": "<current-version>", "babel-preset-react": "<current-version>", "jest": "<current-version>", "react-test-renderer": "<current-version>" }, "scripts": { "test": "jest" }
// .babelrc { "presets": ["es2015", "react"] }
Y así, ¡Estás listo para despegar!
Snapshot Testing #
Vamos a crear un snapshot test para un componente Link que renderé un hyperlink:
// Link.react.js import React from 'react'; const STATUS = { HOVERED: 'hovered', NORMAL: 'normal', }; export default class Link extends React.Component { constructor(props) { super(props); this._onMouseEnter = this._onMouseEnter.bind(this); this._onMouseLeave = this._onMouseLeave.bind(this); this.state = { class: STATUS.NORMAL, }; } _onMouseEnter() { this.setState({class: STATUS.HOVERED}); } _onMouseLeave() { this.setState({class: STATUS.NORMAL}); } render() { return ( <a className={this.state.class} href={this.props.page || '#'} onMouseEnter={this._onMouseEnter} onMouseLeave={this._onMouseLeave}> {this.props.children} </a> ); } }
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 Link from '../Link.react'; import renderer from 'react-test-renderer'; 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(); });
Cuando ejecutas npm test
o jest
, se producirá un archivo de salida como éste:
// __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. El snapshot deberá ser entregado a lo largo de los cambios en 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.
El codigo de éste ejemplo se encuentra disponible en: examples/snapshot.
DOM Testing #
Si desea comprobar y manipuar el render de sus componentes puede usar Enzyme o TestUtils de React. Usaremos Enzyme para este ejemplo.
Tienes que ejecutar npm install --save-dev enzyme
para usar Enzyme. Si usted usa una version menor de React a la 15.5.0, tambien necesitará instalar react-addons-test-utils
.
Vamos a implementar un checkbox que permute entre dos estados:
// CheckboxWithLabel.js import React from 'react'; export default class CheckboxWithLabel extends React.Component { constructor(props) { super(props); this.state = {isChecked: false}; // bind manually because React class components don't auto-bind // http://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html#autobinding this.onChange = this.onChange.bind(this); } onChange() { this.setState({isChecked: !this.state.isChecked}); } render() { return ( <label> <input type="checkbox" checked={this.state.isChecked} onChange={this.onChange} /> {this.state.isChecked ? this.props.labelOn : this.props.labelOff} </label> ); } }
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'); });
El código de este ejemplo está disponible en: examples/enzyme.
Transformers personalizados #
Si necesita funcionalidades mas avanzadas, uedes construir tu propio transformer, en lugar de usar babel-jest, aquí un ejemplo de uso de babel:
// custom-transformer.js 'use strict'; const babel = require('babel-core'); const jestPreset = require('babel-preset-jest'); module.exports = { process(src, filename) { if (babel.util.canCompile(filename)) { return babel.transform(src, { filename, presets: [jestPreset], retainLines: true, }).code; } return src; }, };
No olvide instalar los paquetesbabel-core
y babel-preset-jest
para que éste ejemplo funcione.
Para que esto funcione con Jest necesita actualizar la configuración de Jest con ésto: "transform": {"\\.js$": "path/to/custom-transformer.js"}
.
Si usted desea construir un transformer con soporte con babel, tu puedes usar tambien babel-jest para componer uno y pasarlo a las opciones de tu configuración personalizada:
const babelJest = require('babel-jest'); module.exports = babelJest.createTransformer({ presets: ['my-custom-preset'], });