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でのテストにおけるすべての重要なポイントです。 これをマスターした後、テストのために視野を広げるために、ドキュメントで他のすべてを見ることができます。



All Articles