作業ログ 機能実装その3 2019/09/07

※この記事に登場するソースコードは僕個人が開発しているプロジェクトのもので仕事とは無関係です

歯医者に行ってきました。レーザーか何か熱を使われたっぽくて口のなかが焦げ臭いです。

kenta-s.hatenadiary.jp

この記事の続きをやっていきます。

バックエンド側ができたのでフロント側を実装していきます。

フロントエンドは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のみですが、ドメインドメインしたワードがたくさん出てきそうなのでここは記事には書かずに作っていこうと思います。