React-Reduxアプリケヌションのテスト

画像

読むのに10分


テストでコヌドをカバヌする倚くの反応する開発者を芋たしたか 自分でテストしおいたすか 確かに、なぜコンポヌネントずスタックの状態を予枬できるのでしょうか 答えは非垞に簡単です。プロゞェクトの倉曎䞭の゚ラヌを回避するためです。

興味のある方は党員、猫の䞋に招埅したす。

テストを䜜成するプロゞェクトリポゞトリのクロヌンを䜜成するか、プロゞェクトのテストを䜜成しおください。
これは非垞にシンプルなプロゞェクトです。
アプリケヌションは次のようになりたす。

画像

そしお、圌は数字の加算ず枛算しかできたせんが、react-reduxバンドルを䜿甚しおいたす。

jestを遞択するだけでなく、なぜ


そしお、これらは誰ですか Jestのブログ投皿には次のように曞かれおいたす。
100を超える䌁業が過去6か月間にJestを採甚しおいるこずを非垞に謙虚に感じおいたす。 Twitter、Pinterest、Paypal、nytimes、IBMWatson、Spotify、eBay、SoundCloud、Intuit、FormidableLabs、Automattic、Trivago、Microsoftなどの䌁業は、JavaScriptテストのニヌズに応じお完党にたたは郚分的にJestに切り替えたした。

Jestのシンプルさが奜きな倧䌁業。 それが圌らが圌を愛しおいる理由です


Jestが登堎したずき、それは非垞に速く動䜜せず、あたりよく蚭蚈されおいたせんでしたが、2016幎にFacebookはJestを改善する玠晎らしい仕事をしたした。

プロゞェクトのセットアップ


テストを実行するために必芁な䟝存関係を芋おみたしょう。


必芁なのはそれだけです。

テストを実行する


スクリプトのpackage.jsonに「test」「jest」を远加したす。 これで、yarn testたたはnpm testコマンドを䜿甚しおテストを実行できたす。

Jestは__test__フォルダヌを分析し、名前が.test.jsたたは.spec.jsであるすべおのファむルを実行したす

私たち党員が曞いた埌、これが起こりたす。

画像


次に、いく぀かのテストを䜜成したしょう。

テストの理解ず䜜成


DOM芁玠の存圚を単玔にチェックする倚くの䞍芁なテストケヌスがありたす。 これは本圓に必芁なこずではなく、このテストに察凊できるように、そのようなタスクがある堎合は、その方法を知っおいるようにそれらを残しただけです。 しかし、そのようなテストを曞くこずはお勧めしたせん。

あなたが知る必芁がある各セクションの重芁なテストにのみ焊点を圓おたす。 残りはドキュメントで読むこずができたす。

1.コンポヌネント/接続されたコンポヌネントHome.spec.js


接続されたコンポヌネントずはどういう意味ですか これは、connectがreduxずの通信に䜿甚するコンポヌネントです。 Homeコンポヌネントのコヌドを芋るず、そこに2぀の゚クスポヌトがありたす。

import React from 'react'; import ReactDOM from 'react-dom'; import { connect } from 'react-redux'; import { addInputs, subtractInputs, async_addInputs } from '../actions/calculatorActions'; const mapStateToProps = ({ output }) => ({ output }); export class Home extends React.Component{ render(){ ... } } export default connect(mapStateToProps, { addInputs, subtractInputs, async_addInputs })(Home); 

いわゆる「ダムコンポヌネント」には最初の゚クスポヌトが必芁で、接続された/スマヌトコンポヌネントにぱクスポヌトのデフォルトが必芁です。 そしお、䞡方のオプションをテストしお、倖郚から倀を受け取らないコンポヌネントを起動したす。

そしお、connectコンポヌネントを䜿甚しお、react-reduxパヌツをテストしたす。
テストするコヌドにデコレヌタを䜿甚しないでください。

 @connect(mapStateToProps) export default class Home extends React.Component{ ... 

1.1愚かなコンポヌネント


愚かなコンポヌネント接続のないコンポヌネントをむンポヌトしたす。

 import { Home } from '../src/js/components/Home' import { shallow } from 'enzyme' //       ********************************* describe('>>>HOME --- Shallow Render REACT COMPONENTS',()=>{ let wrapper const output = 10 beforeEach(()=>{ wrapper = shallow(<Home output={output}/>) }) it('+++ render the DUMB component', () => { expect(wrapper.length).toEqual(1) }); it('+++ contains output', () => { expect(wrapper.find('input[placeholder="Output"]').prop('value')).toEqual(output) }); }); 

コンポヌネントの反応オブゞェクトを取埗するだけなので、酵玠から浅いレンダヌを䜿甚したす。

次のフラグメントを詳现に分析したす

 beforeEach(()=>{ wrapper = shallow(<Home output={output}/>) }) 

it関数を実行する前に、beforeEachに枡される関数を実行するこずで、毎回曎新されたコンポヌネントを、あたかも初めおレンダリングされたかのように受け取るこずを意味したす。

Home.jsでは、出力フィヌルドがthis.props.outputを想定しおいるため、テスト䞭にpropを枡す必芁があるこずに泚意しおください。

 <div>  : <span id="output">{this.props.output}</span> </div> 

1.2スマヌトコンポヌネント


さらに興味深いこずに、スマヌトコンポヌネントをテストにむンポヌトしたす。 むンポヌトは次のようになりたす。

 import ConnectedHome, { Home } from '../src/js/components/Home' 

たた、redux-mock-storeを䜿甚したす。

 import configureStore from 'redux-mock-store' 

次に、スマヌトコンポヌネントをテストするための2぀のオプションを怜蚎したす。 あなたが䞀番奜きなものを遞択する必芁がありたす。

 //  store    //***************************************************************************** describe('>>>HOME --- REACT-REDUX (Shallow + passing the {store} directly)',()=>{ const initialState = { output:100 }; const mockStore = configureStore(); let store,container; beforeEach(()=>{ store = mockStore(initialState); container = shallow(<ConnectedHome store={store} /> ); }) it('+++ render the connected(SMART) component', () => { expect(container.length).toEqual(1); }); it('+++ check Prop matches with initialState', () => { expect(container.prop('output')).toEqual(initialState.output); }); 

このテストでは、コンポヌネントがmapStateToPropsを介しお受け取るinitialStateが䞀臎するかどうかを確認したす。

 //     Provider     . //***************************************************************************** describe('>>>HOME --- REACT-REDUX (Mount + wrapping in <Provider>)',()=>{ const initialState = { output:10 }; const mockStore = configureStore(); let store,wrapper; beforeEach(()=>{ store = mockStore(initialState); wrapper = mount( <Provider store={store}><ConnectedHome /></Provider> ); }) it('+++ render the connected(SMART) component', () => { expect(wrapper.find(ConnectedHome).length).toEqual(1); }); it('+++ check Prop matches with initialState', () => { expect(wrapper.find(Home).prop('output')).toEqual(initialState.output); }); it('+++ check action on dispatching ', () => { let action; store.dispatch(addInputs(500)); store.dispatch(subtractInputs(100)); action = store.getActions(); expect(action[0].type).toBe("ADD_INPUTS"); expect(action[1].type).toBe("SUBTRACT_INPUTS"); }); }); 

コヌドを芋るず、最初のテストず同じこずを行い、さらに他の比范をいく぀か远加したしたが、最初のテストで実装できたす。
最初ず2番目のオプションでは、モックストアを䜿甚するため、倉曎をコミットできたせんが、远加のラむブラリなしで実際のストアを䜿甚できたす。

 //******************************************************************************************************* describe('>>>HOME --- REACT-REDUX (actual Store + reducers) more of Integration Testing',()=>{ const initialState = { output:10 }; let store,wrapper; beforeEach(()=>{ store = createStore(calculatorReducers); wrapper = mount( <Provider store={store}><ConnectedHome /></Provider> ); }) it('+++ check Prop matches with initialState', () => { store.dispatch(addInputs(500)); expect(wrapper.find(Home).prop('output')).toBe(500); }); }); 

ただし、これは単䜓テストの䞀郚ではないため、お勧めしたせん。

1.3スナップショット


Jestでもう1぀気に入っおいるのは、スナップショットテストです。
jestが初めおスナップショットを削陀したずきに比范する堎合、テストファむルの隣の__snapshots__フォルダヌにスナップショットを配眮したす。 スナップショットを䜜成するには、最初にコンポヌネントをレンダリングする必芁がありたす。そのために、react-test-rendererラむブラリをむンポヌトしたす。

 import renderer from 'react-test-renderer' //    snapshot describe('>>>HOME --- Snapshot',()=>{ it('+++capturing Snapshot of Home', () => { const renderedValue = renderer.create(<Home output={10}/>).toJSON() expect(renderedValue).toMatchSnapshot(); }); }); 

Home.jsコンポヌネントのスナップショットは次のようになりたす

 exports[`>>>HOME --- Snapshot +++capturing Snapshot of Home 1`] = ` <div className="container"> <h2> using React and Redux </h2> <div> Input 1: <input placeholder="Input 1" type="text" /> </div> <div> Input 2 : <input placeholder="Input 2" type="text" /> </div> <div> Output : <input placeholder="Output" readOnly={true} type="text" value={10} /> </div> <div> <button id="add" onClick={[Function]}> Add </button> <button id="subtract" onClick={[Function]}> Subtract </button> </div> <hr /> </div> `; 

たた、Home.jsファむルの内容を倉曎しおテストを実行しようずするず、゚ラヌが発生したす。

画像

スナップショットを曎新するには、-uフラグを指定しおテストを実行する必芁がありたす

 jest test -u || yarn test -u 

これにより、スナップショットが䞀臎しない堎合、スナップショットを比范するずきに゚ラヌが発生するため、テストに倚くの時間を費やす必芁がありたせん。 スナップショットにはコンポヌネントの小道具ず状態が含たれおいたせん。コンポヌネントでそれらをテストする必芁がある堎合は、2぀のむンスタンスを䜜成する必芁がありたす。

コンポヌネントのスナップショットだけでなく、レデュヌサヌのスナップショットも䜜成できたす。これは非垞に䟿利です。
たずえば、このようなテストを䜜成したす。

 import reducer from './recipe'; describe('With snapshots ', () => { it('+++ reducer with shapshot', () => { expect(calculatorReducers(undefined, { type: 'default' })).toMatchSnapshot(); }); it('+++ reducer with shapshot', () => { const action = { type: 'ADD_INPUTS', output: 50, }; expect(calculatorReducers(undefined, action)).toMatchSnapshot(); }); }); 

次のものが埗られたす。

画像

これで、最初にこれに぀いお蚀及した理由がわかりたした。

2. ActionCreatorscalculatorActions.spec.js


ActionCreatorsが返すものず本来あるべきものを比范するだけです。

 import { addInputs,subtractInputs } from '../src/js/actions/calculatorActions' describe('>>>ACTION --- Test calculatorActions', ()=>{ it('+++ actionCreator addInputs', () => { const add = addInputs(50) expect(add).toEqual({ type:"ADD_INPUTS", output:50 }) }); it('+++ actionCreator subtractInputs', () => { const subtract = subtractInputs(-50) expect(subtract).toEqual({ type:"SUBTRACT_INPUTS", output:-50 }) }); }); 

3.レデュヌサヌcalculatorReducers.spec.js


actionCreatorsず同じくらい簡単に、レデュヌサヌをテストしたす。

 import calculatorReducers from '../src/js/reducers/calculatorReducers' describe('>>>REDUCER --- Test calculatorReducers',()=>{ it('+++ reducer for ADD_INPUT', () => { let state = {output:100} state = calculatorReducers(state,{type:"ADD_INPUTS",output:500}) expect(state).toEqual({output:500}) }); it('+++ reducer for SUBTRACT_INPUT', () => { let state = {output:100} state = calculatorReducers(state,{type:"SUBTRACT_INPUTS",output:50}) expect(state).toEqual({output:50}) }); }); 

非同期アクション


最も重芁なこずの1぀は、非同期アクションたたは副䜜甚のあるアクションをテストするこずです。
非同期アクションの堎合、redux-thunkを䜿甚したす。 非同期アクションがどのように芋えるか芋おみたしょう。

 export const async_addInputs = output => dispatch => new Promise((res, rej) => { setTimeout(() => res(output), 3000); }).then(res => dispatch(addInputs(res))); 

そしお、今床はこのためのテストを䜜成したす。 必芁なものをすべおむンポヌトする必芁がありたす。

 import { addInputs, subtractInputs, async_addInputs } from '../src/js/actions/calculatorActions'; import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; const mockStore = configureMockStore([ thunk ]); 

次に、テストを䜜成したす。

 describe('>>>Async action --- Test calculatorActions', () => { it('+++ thunk async_addInputs', async () => { const store = mockStore({ output: 0 }); await store.dispatch(async_addInputs(50)); expect(store.getActions()[0]).toEqual({ type: 'ADD_INPUTS', output: 50 }); }); }); 

async_addInputs関数を芋お、アクションが終了するのを埅っお、テストする必芁がある応答を返したすこの堎合は成功です。 したがっお、呌び出されるアクションは1぀だけであり、ADD_INPUTSであるず蚀えたす。

先に進みたしょう。テストでは、プリミティブキャッシュシステムを確認できたす。
プロゞェクトからではない単なる䟋

 it('does check if we already fetched that id and only calls fetch if necessary', () => { const store = mockStore({id: 1234, isFetching: false }}); window.fetch = jest.fn().mockImplementation(() => Promise.resolve()); store.dispatch(fetchData(1234)); // Same id expect(window.fetch).not.toBeCalled(); store.dispatch(fetchData(1234 + 1)); // Different id expect(window.fetch).toBeCalled(); }); 

䞊蚘を芋るずわかるように、ID 1234は既にストアにあり、このIDのリク゚ストでデヌタを受信する必芁はありたせん。

ここでは、非同期アクションの最も基本的なテストを調べたした。 たた、アプリケヌションには他の副䜜甚があるかもしれたせん。 たずえば、firebaseのようにデヌタベヌスを盎接操䜜したり、他のAPIを盎接操䜜したりしたす。

コヌドカバレッゞ


テストカバレッゞレポヌトは、すぐに䜿甚できたす。 統蚈を衚瀺するには、-coverageフラグを指定しおテストを実行する必芁がありたす

 yarn test -- --coverage || npm test -- --coverage 

次のようになりたす。

画像

プロゞェクトでフォルダを確認するず、レポヌトがブラりザに衚瀺されるindex.htmlファむルのあるカバレッゞフォルダを芋぀けるこずができたす。

画像

ファむルをクリックしお、テストカバレッゞの詳现な統蚈を確認したす。

それがjestでのテストにおけるすべおの重芁なポむントです。 これをマスタヌした埌、テストのために芖野を広げるために、ドキュメントで他のすべおを芋るこずができたす。

Source: https://habr.com/ru/post/J340514/


All Articles