Если вы используете Webpack в своем проекте, вы, вероятно, захотите использовать файл bundle.js для тестирования компонентов React во время цикла TDD. Пакет mocha-webpack упрощает эту задачу:
npm i mocha mocha-webpack enzyme jsdom sinon chai --save-dev
Нам нужен новый файл с именем mocha-webpack.opts в нашем корневом каталоге, чтобы сохранить наши параметры mocha-webpack:
--colors --require ignore-styles --require babel-core/register --require jsdom-global/register
Наш компонент React:
'use strict' import { connect } from 'react-redux' import { render } from 'react-dom' import * as TestsActionCreators from '../actions/tests' import React, { Component, PropTypes } from 'react' import { Link, browserHistory, withRouter} from 'react-router' import { Button, Modal } from 'react-bootstrap' // export for unconnected component (for mocha tests) export class QuestionsComponent extends Component { constructor(props) { super(props); this.state = { questions: [], test_id: this.props.routeParams.test_id } } //Test mocha: console.log('This props >>>>>>' + JSON.stringify(this.props)); } /** * Load test data and questions **/ componentWillMount() { if ( ! this.props.OneTestArrayProp.length ) { let action = TestsActionCreators.fetchOneTest( this.props.routeParams.test_id ); this.props.dispatch(action); } } render() { return ( <div className="container_div"> {this.props.QuestionsArrayProp.map((q, i) => <div key={i} className="questions_div"> <div><b>{i+1}.- Question</b>: {q.question} -- {q.id} </div> </div> )} </div> { this.props.children } </div> ) } } QuestionsComponent.propTypes = { QuestionsArrayProp: PropTypes.array } QuestionsComponent.defaultProps = { QuestionsArrayProp: [] } const mapStateToProps = (state) => { return { QuestionsArrayProp: state.rootReducer.tests_rdcr.QuestionsArrayProp } } export default withRouter(connect(mapStateToProps)(QuestionsComponent));
Наш файл webpack для использования в тестовой среде:
// file: webpack.testing.config.js path = require('path'); var webpack = require('webpack'); module.exports = { entry: { app: ["./entry.jsx"] }, target: 'web', debug: 'devel', devtool: 'eval', output: { path: path.resolve(__dirname, "build"), publicPath: "/build/", filename: "bundle.js" }, resolve: { extensions: ['', '.js', '.jsx'] }, module: { loaders: [ {test: /sinon.js$/, loader: "imports?define=>false" }, {test: /\.jsx?$/, loader: 'babel', exclude: /node_modules/, query: { cacheDirectory: true, presets: ["react", "es2015", "stage-0"] }, include: path.app }, { test: /\.css$/, loader: 'style-loader!css-loader'}, { test: /\.js$/, loader: 'babel', exclude: /node_modules/, query: { presets: ["es2015", "stage-0"] }}, { test: /\.less$/, loader: 'style!css!less' }, { test: /\.scss$/, loader: 'style!css!sass' }, { test: /\.json$/, loader: "json-loader" }, { test: /\.woff(2)?(\?v=[0-9].[0-9].[0-9])?$/, loader: "url-loader?mimetype=application/font-woff" }, { test: /\.(ttf|eot|svg)(\?v=[0-9].[0-9].[0-9])?$/, loader: "file-loader?name=[name].[ext]" }, { test: /\.gif$/, loader: "url-loader?mimetype=image/png" }, { test: /bootstrap-sass\/assets\/javascripts\//, loader: 'imports?jQuery=jquery' } // Bootstrap 3 ] }, externals: [ { 'isomorphic-fetch': { root: 'isomorphic-fetch', commonjs2: 'isomorphic-fetch', commonjs: 'isomorphic-fetch', amd: 'isomorphic-fetch' }, 'cheerio': 'window', 'react/addons': true, 'react/lib/ExecutionEnvironment': true, 'react/lib/ReactContext': true } ], node: { fs: "empty" }, plugins: [ new webpack.ProvidePlugin({ $: "jquery", jQuery: "jquery" }), new webpack.IgnorePlugin(/ReactContext|react\/addons/), new webpack.optimize.DedupePlugin() ] };
Как вы, наверное, уже знаете, одним из преимуществ mocha является то, что у него нет собственного DOM, поэтому мы должны загрузить DOM с помощью помощника:
// file: __tests__/helpers/browser.js var baseDOM = '<!DOCTYPE html><html><head><meta charset="utf-8"></head><body></body></html>'; var jsdom = require('jsdom').jsdom; global.document = jsdom(baseDOM); global.window = document.defaultView; if ( global.self != null) { console.log(' global.self >>>>> ' + global.self); } else { global.self = global.this; } global.navigator = { userAgent: 'node.js' };
Наш файл mocha для тестирования класса React выглядит так:
'use strict'; // Mocha TEST // file: __tests__/components/QuestionComponent.spec.js import ReactDOM from 'react-dom'; import React from 'react'; import { expect } from 'chai'; import { mount, shallow, render } from 'enzyme'; import { QuestionsComponent } from '../../components/QuestionsComponent'; import sinon from 'sinon'; var dispatch = function() { console.log('>>>>>>>> Mocking dispatch '); }; var props = { dispatch: dispatch, routeParams: {test_id: 1}, tests_rdcr: {} }; describe('QuestionsComponent', function () { it('QuestionsComponent calls componentWillMount', () => { sinon.spy(QuestionsComponent.prototype, 'componentWillMount'); const enzymeWrapper = mount(<QuestionsComponent {...props} />); expect(QuestionsComponent.prototype.componentWillMount.calledOnce).to.equal(true); }); it("QuestionsComponent does not render questions_div", function() { expect(shallow(<QuestionsComponent {...props} />).contains(<div className="questions_div" />)).to.equal(false); }); it("QuestionsComponent does not render questions_div", function() { var answers = [{id:1, answer: 'answer uno', correct: true, active: true, question_id: 1}, {id:2, answer: 'answer dos ', correct: true, active: true, question_id: 1}, {id:3, answer: 'answer tres', correct: true, active: true, question_id: 1} ]; props['QuestionsArrayProp'] = answers; expect(mount(<QuestionsComponent {...props} />).find('.questions_div').length).to.equal(3); }); });
Теперь одна из проблем с реальным компонентом React заключается в том, что компонент переплетен с Redux и React-Router, и нам нужно изолировать компонент, чтобы выполнить модульное тестирование. Добиться этого действия на удивление легко. Обычно в рабочей среде мы импортируем компонент следующим образом:
import QuestionsComponent from './components/QuestionsComponent'
Отсутствие фигурных скобок вокруг имени компонента означает, что мы импортируем экспортируемый элемент по умолчанию, id est:
export default withRouter(connect(mapStateToProps)(QuestionsComponent));
Но если мы добавим оператор экспорта не по умолчанию в наш класс React:
// export for unconnected component (for mocha tests) export class QuestionsComponent extends Component {
а затем мы импортируем с помощью фигурных скобок:
import { QuestionsComponent } from './components/QuestionsComponent'
это означает, что теперь мы можем загрузить и протестировать компонент, изолированный от Redux и React-Router. Довольно круто, ха?
Теперь мы можем протестировать наш компонент:
./node_modules/mocha-webpack/bin/mocha-webpack --opts ./mocha-webpack.opts --webpack-config ./webpack.testing.config.js -r __tests__/helpers/browser.js __tests__/components/QuestionComponent.spec.js
Если все в порядке, вы должны увидеть что-то вроде: