私はこのチュートリアルに従っています: http://reactkungfu.com/2015/07/approaches-to-testing-react-components-an-overview/
「浅いレンダリング」がどのように機能するかを学習しようとしています。
より高次のコンポーネントがあります:
_import React from 'react';
function withMUI(ComposedComponent) {
return class withMUI {
render() {
return <ComposedComponent {...this.props}/>;
}
};
}
_
そしてコンポーネント:
_@withMUI
class PlayerProfile extends React.Component {
render() {
const { name, avatar } = this.props;
return (
<div className="player-profile">
<div className='profile-name'>{name}</div>
<div>
<Avatar src={avatar}/>
</div>
</div>
);
}
}
_
そしてテスト:
_describe('PlayerProfile component - testing with shallow rendering', () => {
beforeEach(function() {
let {TestUtils} = React.addons;
this.TestUtils = TestUtils;
this.renderer = TestUtils.createRenderer();
this.renderer.render(<PlayerProfile name='user'
avatar='avatar'/>);
});
it('renders an Avatar', function() {
let result = this.renderer.getRenderOutput();
console.log(result);
expect(result.type).to.equal(PlayerProfile);
});
});
_
result
変数はthis.renderer.getRenderOutput()
を保持します
チュートリアルでは、_result.type
_は次のようにテストされます。
expect(result.type).toEqual('div');
私の場合、result
をログに記録すると、次のようになります。
LOG: Object{type: function PlayerProfile() {..}, .. }
だから私は私のテストを次のように変更しました:
expect(result.type).toEqual(PlayerProfile)
今それは私にこのエラーを与えます:
_Assertion Error: expected [Function: PlayerProfile] to equal [Function: withMUI]
_
したがって、PlayerProfile
の型は高次関数withMUI
です。
PlayerProfile
で装飾されたwithMUI
、浅いレンダリングを使用して、PlayerProfile
コンポーネントのみがレンダリングされ、その子はレンダリングされません。したがって、浅いレンダリングは、私が想定する装飾されたコンポーネントでは機能しません。
私の質問は:
なぜチュートリアルでは_result.type
_がdivであることが期待されていますが、私の場合はそうではありません。
浅いレンダリングを使用して、より高次のコンポーネントで装飾されたReactコンポーネントをテストするにはどうすればよいですか?
できません。最初に、デコレータを少し脱糖しましょう。
let PlayerProfile = withMUI(
class PlayerProfile extends React.Component {
// ...
}
);
withMUIは別のクラスを返すため、PlayerProfileクラスはwithMUIのクロージャ内にのみ存在します。
これは簡単なバージョンです。
var withMUI = function(arg){ return null };
var PlayerProfile = withMUI({functionIWantToTest: ...});
あなたは関数に値を渡します、それはそれを返しません、あなたは値を持っていません。
ソリューション?それへの参照を保持します。
// no decorator here
class PlayerProfile extends React.Component {
// ...
}
次に、コンポーネントのラップされたバージョンとラップされていないバージョンの両方をエクスポートできます。
// this must be after the class is declared, unfortunately
export default withMUI(PlayerProfile);
export let undecorated = PlayerProfile;
このコンポーネントを使用する通常のコードは変更されませんが、テストではこれを使用します。
import {undecorated as PlayerProfile} from '../src/PlayerProfile';
別の方法は、withMUI関数を(x) => x
(識別関数)にモックすることです。これは奇妙な副作用を引き起こす可能性があり、テスト側から行う必要があるため、デコレータが追加されると、テストとソースが同期しなくなる可能性があります。
ここでは、デコレータを使用しないことが安全なオプションのようです。
Enzymeを使用して、dive()と呼ばれるメソッドでShallowで高次/デコレーターをテストする
このリンクをたどって、ダイビングの仕組みを確認してください
https://github.com/airbnb/enzyme/blob/master/docs/api/ShallowWrapper/dive.md
したがって、高次のコンポーネントを浅くしてから、内部に潜ることができます。
const wrapper=shallow(<PlayerProfile name={name} avatar={}/>)
expect(wrapper.find("PlayerProfile").dive().find(".player-profile").length).toBe(1)
同様に、プロパティにアクセスしてテストすることができます。
'babel-plugin-remove-decorators'プラグインを使用できます。このソリューションでは、装飾されたコンポーネントと装飾されていないコンポーネントをエクスポートせずに、コンポーネントを通常どおりに作成できます。
最初にプラグインをインストールしてから、次の内容のファイルを作成します。「babelTestingHook.js」と呼びます。
require('babel/register')({
'stage': 2,
'optional': [
'es7.classProperties',
'es7.decorators',
// or Whatever configs you have
.....
],
'plugins': ['babel-plugin-remove-decorators:before']
});
以下のようにテストを実行すると、デコレータが無視され、コンポーネントを通常どおりテストできます
mocha ./tests/**/*.spec.js --require ./babelTestingHook.js --recursive
上記の例は、decorator
の概念が「高次コンポーネント」の概念と交換可能に使用されているため、混乱を招くと思います。私は一般的にそれらを組み合わせて使用し、テスト/再配線/モッキングを容易にします。
私はデコレーターを使用します:
より高次のコンポーネントを使用する場所
再配線の問題は、デコレーターの場合のように、エクスポートされた関数/クラスの外部で適用されるものを再配線できないと思います。
デコレータと高次コンポーネントの組み合わせを使用したい場合は、次のようなことができます。
//withMui-decorator.jsx
function withMUI(ComposedComponent) {
return class withMUI extends Component {
constructor(props) {
super(props);
this.state = {
store1: ///bind here based on some getter
};
}
render() {
return <ComposedComponent {...this.props} {...this.state} {...this.context} />;
}
};
}
//higher-order.jsx
export default function(ChildComp) {
@withMui //provide store bindings
return class HOC extends Component {
static childContextTypes = {
getAvatar: PropTypes.func
};
getChildContext() {
let {store1} = this.props;
return {
getAvatar: (id) => ({ avatar: store1[id] });
};
}
}
}
//child.js
export default Child extends Component {
static contextTypes = {
getAvatar: PropTypes.func.isRequired
};
handleClick(id, e) {
let {getAvatar} = this.context;
getAvatar(`user_${id}`);
}
render() {
let buttons = [1,2,3].map((id) => {
return <button type="text" onClick={this.handleClick.bind(this, id)}>Click Me</button>
});
return <div>{buttons}</div>;
}
}
//index.jsx
import HOC from './higher-order';
import Child from './child';
let MyComponent = HOC(Child);
React.render(<MyComponent {...anyProps} />, document.body);
その後、テストしたい場合は、デコレーターがエクスポートされた高次コンポーネントの内部にあるため、デコレーターから提供されたストアを簡単に「再配線」できます。
//spec.js
import HOC from 'higher-order-component';
import Child from 'child';
describe('rewire the state', () => {
let mockedMuiDecorator = function withMUI(ComposedComponent) {
return class withMUI extends Component {
constructor(props) {
super(props);
this.state = {
store1: ///mock that state here to be passed as props
};
}
render() {
//....
}
}
}
HOC.__Rewire__('withMui', mockedMuiDecorator);
let MyComponent = HOC(Child);
let child = TestUtils.renderIntoDocument(
<MyComponent {...mockedProps} />
);
let childElem = React.findDOMNode(child);
let buttons = childElem.querySelectorAll('button');
it('Should render 3 buttons', () => {
expect(buttons.length).to.equal(3);
});
});
これで元の質問に実際に答えられないと確信していますが、デコレータと高次コンポーネントを使用する場合の調整に問題があると思います。
いくつかの良いリソースはここにあります:
私の場合、デコレータは非常に便利であり、アプリケーションでそれらを削除したくない(またはラップされたバージョンとラップされていないバージョンを返す)必要はありません。
私の考えでは、これを行う最良の方法は、babel-plugin-remove-decorators
(テストでそれらを削除するために使用できます)を使用することです。
'use strict';
var babel = require('babel-core');
module.exports = {
process: function(src, filename) {
// Ignore files other than .js, .es, .jsx or .es6
if (!babel.canCompile(filename)) {
return '';
}
if (filename.indexOf('node_modules') === -1) {
return babel.transform(src, {
filename: filename,
plugins: ['babel-plugin-remove-decorators:before']
}).code;
}
return src;
}
};
babel.transform
要素を配列値として渡すbabel-plugin-remove-decorators:before
呼び出しに注意してください。以下を参照してください。 https://babeljs.io/docs/usage/options/
これをJest(私が使用したもの)に接続するには、package.json
で以下のような設定を行うことができます。
"jest": {
"rootDir": "./src",
"scriptPreprocessor": "../preprocessor.js",
"unmockedModulePathPatterns": [
"fbjs",
"react"
]
},
ここで、preprocessor.js
はプリプロセッサの名前です。