※この記事に登場するソースコードは僕個人が開発しているプロジェクトのもので仕事とは無関係です
歯医者に行ってきました。レーザーか何か熱を使われたっぽくて口のなかが焦げ臭いです。
この記事の続きをやっていきます。
バックエンド側ができたのでフロント側を実装していきます。
フロントエンドはReact/Reduxを使っていますが、ReactもReduxもWebpackもチームで使った経験がほぼなくて自己流でやってます。誰かReact教えてほしいです。。。
いつでもすぐそばにjQuery
実装していく
まずは非同期アクションのテストを書きます。
redux-flashの @@redux-flash/FLASH
というアクションが作られることを期待するテストになります。
import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import moxios from 'moxios'; import expect from 'expect'; import { transferFolder } from '../../../app/javascript/packs/redux/actions' const middlewares = [thunk]; const mockStore = configureMockStore(middlewares); describe('transferFolder', () => { beforeEach(function() { moxios.install(); }); afterEach(function() { moxios.uninstall(); }); it('create @@redux-flash/FLASH', () => { moxios.wait(() => { const request = moxios.requests.mostRecent() request.respondWith({ status: 201, response: {"messages":["hoge@example.comに3個の画像を送信しました"],"error_messages":[]} }); }) const expectedActions = [{"payload": {"message": "hoge@example.comに3個の画像を送信しました", "options": {}}, "type": "@@redux-flash/FLASH"}] const store = mockStore({}) return store.dispatch(transferFolder('dummy_token', 'hoge@example.com', 1)).then(() => { expect(store.getActions()).toEqual(expectedActions) }) }) })
想定通り落ちます
$ jest test/javascript/actions/transferFolder.test.js FAIL test/javascript/actions/transferFolder.test.js transferFolder ✕ create @@redux-flash/FLASH (3ms) ● transferFolder › create @@redux-flash/FLASH TypeError: (0 , _actions.transferFolder) is not a function 29 | const store = mockStore({}) 30 | > 31 | return store.dispatch(transferFolder('dummy_token', 'hoge@example.com', 1)).then(() => { | ^ 32 | expect(store.getActions()).toEqual(expectedActions) 33 | }) 34 | }) at Object.<anonymous> (test/javascript/actions/transferFolder.test.js:31:27) Test Suites: 1 failed, 1 total Tests: 1 failed, 1 total Snapshots: 0 total Time: 2.328s
続いて、非同期アクションを書いていきます。
現在redux-thunkを使っています。
csrf tokenは呼び出し側から渡します。
もっと良い方法がありそうだなとは思いつつこんな感じの実装になりました。
export function transferFolder(csrftoken, usernamesOrEmails, folderId) { axios.defaults.headers.common['X-CSRF-Token'] = csrftoken return dispatch => { return axios.post(`/api/v1/folder_transfer`, { folder_transfer: { recipient_usernames_or_emails: usernamesOrEmails, folder_id: folderId, } } ).then(response => { for(let message of response.data.messages){ dispatch(flashMessage(message)) } for(let message of response.data.error_messages){ dispatch(flashMessage(message, {isError: true})) } return response }) } }
パスするようになりました。
$ jest test/javascript/actions/transferFolder.test.js PASS test/javascript/actions/transferFolder.test.js transferFolder ✓ create @@redux-flash/FLASH (106ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 1.6s, estimated 2s
エラーメッセージがあるケースも追加します
it('create @@redux-flash/FLASH twice when messages and error_messages both have elements', () => { moxios.wait(() => { const request = moxios.requests.mostRecent() request.respondWith({ status: 201, response: {"messages":["hoge@example.comに3個の画像を送信しました"],"error_messages":["ユーザーが見つかりませんでした: foo"]} }); }) const expectedActions = [ {"payload": {"message": "hoge@example.comに3個の画像を送信しました", "options": {}}, "type": "@@redux-flash/FLASH"}, {"payload": {"message": "ユーザーが見つかりませんでした: foo", "options": {isError: true}}, "type": "@@redux-flash/FLASH"}, ] const store = mockStore({}) return store.dispatch(transferFolder('dummy_token', 'hoge@example.com', 1)).then(() => { expect(store.getActions()).toEqual(expectedActions) }) })
これも対応するように書いていたのでパスしました。
$ jest test/javascript/actions/transferFolder.test.js PASS test/javascript/actions/transferFolder.test.js transferFolder ✓ create @@redux-flash/FLASH (123ms) ✓ create @@redux-flash/FLASH twice when messages and error_messages both have elements (103ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 0 total Time: 1.701s, estimated 2s
つぎはステータスがエラー時。
この関数では例え処理に失敗した場合でも201が返るはずで、20x以外だと何か想定外のことが起きていると考えられるため、ボディのmessagesがどうなっているかにかかわらずエラーメッセージが返ることを期待するテストにします。
it('creaet @@redux-flash/FLASH with server error message', () => { moxios.wait(() => { const request = moxios.requests.mostRecent() request.respondWith({ status: 422, response: {"error_messages":["ユーザーが見つかりませんでした: foo"]} }); }) const expectedActions = [ {"payload": {"message": "サーバーエラーが発生しました", "options": {isError: true}}, "type": "@@redux-flash/FLASH"}, ] const store = mockStore({}) return store.dispatch(transferFolder('dummy_token', 'hoge', 1)).then(() => { expect(store.getActions()).toEqual(expectedActions) }) })
これは想定通り落ちます。
$ jest test/javascript/actions/transferFolder.test.js FAIL test/javascript/actions/transferFolder.test.js transferFolder ✓ create @@redux-flash/FLASH (122ms) ✓ create @@redux-flash/FLASH twice when messages and error_messages both have elements (104ms) ✕ creaet @@redux-flash/FLASH with server error message (143ms) ● transferFolder › creaet @@redux-flash/FLASH with server error message Request failed with status code 422 57 | moxios.wait(() => { 58 | const request = moxios.requests.mostRecent() > 59 | request.respondWith({ | ^ 60 | status: 422, 61 | response: {"error_messages":["ユーザーが見つかりませんでした: foo"]} 62 | }); at createError (node_modules/moxios/dist/webpack:/~/axios/lib/core/createError.js:15:1) at settle (node_modules/moxios/dist/webpack:/~/axios/lib/core/settle.js:18:1) at Request.respondWith (node_modules/moxios/dist/webpack:/index.js:250:5) at respondWith (test/javascript/actions/transferFolder.test.js:59:15) at Timeout.callback [as _onTimeout] (node_modules/jsdom/lib/jsdom/browser/Window.js:678:19) Test Suites: 1 failed, 1 total Tests: 1 failed, 2 passed, 3 total Snapshots: 0 total Time: 1.887s, estimated 2s
テストが通るように修正します。
export function transferFolder(csrftoken, usernamesOrEmails, folderId) { axios.defaults.headers.common['X-CSRF-Token'] = csrftoken return dispatch => { return axios.post(`/api/v1/folder_transfer`, { folder_transfer: { recipient_usernames_or_emails: usernamesOrEmails, folder_id: folderId, } } ).then(response => { for(let message of response.data.messages){ dispatch(flashMessage(message)) } for(let message of response.data.error_messages){ dispatch(flashMessage(message, {isError: true})) } return response }) .catch(error => { dispatch(flashMessage("サーバーエラーが発生しました", {isError: true})) return error }) } }
これでエラーの場合もパスしました。
$ jest test/javascript/actions/transferFolder.test.js PASS test/javascript/actions/transferFolder.test.js transferFolder ✓ create @@redux-flash/FLASH (106ms) ✓ create @@redux-flash/FLASH twice when messages and error_messages both have elements (102ms) ✓ creaet @@redux-flash/FLASH with server error message (102ms) Test Suites: 1 passed, 1 total Tests: 3 passed, 3 total Snapshots: 0 total Time: 1.956s
これでasync actionも完成しました。
残るはcomponentのみですが、ドメインドメインしたワードがたくさん出てきそうなのでここは記事には書かずに作っていこうと思います。