JestでNuxt.js(Vue.js)のVuexをテスト

みなさん、フロントエンドのテスト書いていますか？

Nuxt.js(Vue.js)でJestを使ってコンポーネントのテストについては情報がいろいろありましたが、VuexのStoreのテストについてあまり情報がなかったので、今回はVuexStoreのテストについてまとめました。

「[Nuxt v2とFirebase(CloudFirestore)でPWA対応Webアプリ開発](https://qiita.com/_takeshi_24/items/3ee051e1db1b3e8da106)」で作成したアプリケーションをJestでテストする手順についてまとめていきます。

## テストの準備
### テストに必要なツール
#### Jest
![jest.png](https://qiita-image-store.s3.amazonaws.com/0/3003/bb1d3444-d796-6ce5-74e3-b1564f771e3f.png)
https://github.com/facebook/jest

JestはFacebookが開発を進めるフロントエンドのテストランナです。
Facebook製なので当然Reactに対応していますが、その他JavaScriptアプリケーションのテストにも使用でき、Vue.jsでも利用できます。

RSpecライクな記法でテストコーディングが可能です。

#### Vue Test Utils
https://vue-test-utils.vuejs.org/ja/
Vue.jsアプリケーション用の公式単体テストライブラリです。
Vue-Router、VuexなどVue.jsのテストコーディングのために必要です。

#### Babel (@babel/core)
BabelはES2015以降のバージョンで記載されたJavaScriptをECMAScript5に変換するためのトランスコンパイラです。

#### babel-jest
JestをBabelに対応するためのツールです。

#### babel-preset-env
環境に従って必要なBabelプラグインを自動で決定するツールです。

#### babel-preset-vue-app
Vue開発のためのBabel設定を提供するツールです。

#### lodash.clonedeep
テスト実行のために直接必要になるわけではありませんが、VuexのテストでStoreオブジェクトをコピーするために利用します。

### 必要なツールを設定
必要なツールをインストールします。

```terminal
> yarn add jest @vue/test-utils lodash.clonedeep babel-jest @babel/core @babel/preset-env babel-preset-vue-app lodash.clonedeep --dev
```

package.jsonにtestコマンド、Jest、Babelの設定を追加します。
targets->browsersオプションで対象ブラウザを指定しています。

オプションの詳細はこの辺りに記載があります。
https://babeljs.io/docs/en/babel-preset-env

```json:/package.json
  "scripts": {
    "test": "jest"
  },
  "jest": {
    "transform": {
      "^.+\\.js$": "babel-jest"
    }
  },
  "babel": {
    "presets": [
      [
        "@babel/preset-env",
        {
          "targets": {
            "browsers": [
              "last 2 versions"
            ]
          },
          "debug": true
        }
      ]
    ]
  },
```

この状態でテストを実行してみます。

```terminal
> yarn run test
yarn run v1.10.1
$ jest
No tests found
```
まだテストコードがないので、エラーになりますが、テストが実行されています。

## テストコーディング
### Vuexのテスト
VuexのStoreのテストはこちらの書籍を参考にしました。
<img width="180" src="https://qiita-image-store.s3.amazonaws.com/0/3003/8c9d6059-1e28-ad18-f24f-6854fd14073c.jpeg"/>
[Nuxt.jsビギナーズガイド]
(https://www.amazon.co.jp/Nuxt-js%E3%83%93%E3%82%AE%E3%83%8A%E3%83%BC%E3%82%BA%E3%82%AC%E3%82%A4%E3%83%89%E2%80%95Vue-js-%E3%83%99%E3%83%BC%E3%82%B9%E3%81%AE%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%83%AF%E3%83%BC%E3%82%AF%E3%81%AB%E3%82%88%E3%82%8B%E3%82%B7%E3%83%B3%E3%82%B0%E3%83%AB%E3%83%9A%E3%83%BC%E3%82%B8%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E9%96%8B%E7%99%BA-%E8%8A%B1%E8%B0%B7-%E6%8B%93%E7%A3%A8/dp/4863542569/ref=sr_1_1?ie=UTF8&qid=1544104962&sr=8-1&keywords=nuxt.js%E3%83%93%E3%82%AE%E3%83%8A%E3%83%BC%E3%82%BA%E3%82%AC%E3%82%A4%E3%83%89)

[GitHub](https://github.com/potato4d/nuxt-beginners-guide/blob/master/packages/nuxt-testing-environment/spec/store/index.spec.js)にソースも公開されています。

```js:/spec/store/messages.spec.js
require('dotenv').config()

const Vuex = require('vuex')
const messages = require('../../store/messages')
const { createLocalVue } = require('@vue/test-utils')
const cloneDeep = require('lodash.clonedeep')

const localVue = createLocalVue()
localVue.use(Vuex)

describe('store/message.js', () => {
  let store
  
  beforeEach(() => {
    store = new Vuex.Store(cloneDeep(messages))
  })
  
  describe('actions', () => {
    test('initMessagesでmessagesが初期化される', async () => {
      expect(store.getters['messages'].length).toBe(0)
      await store.dispatch('initMessages')
      expect(store.getters['messages'].length).not.toBe(0)
    })
  })
})
```

少し説明すると、```require('dotenv').config()```で、.envの値を読み込みます。

beforeEachで、cloneDeepを使ってmessagesのStoreをコピーし、オブジェクトを生成します。

```js
  beforeEach(() => {
    store = new Vuex.Store(cloneDeep(messages))
  })
```

実際のテストはこちらです。
StoreのinitMessagesアクションをdispatchして、messagesステートにデータがセットされることを確認しています。

```js
  describe('actions', () => {
    test('initMessagesでmessagesが初期化される', async () => {
      expect(store.getters['messages'].length).toBe(0)
      await store.dispatch('initMessages')
      expect(store.getters['messages'].length).not.toBe(0)
    })
  })
```

テストを実行します。

```console
 PASS  spec/store/messages.spec.js
  store/message.js
    actions
      ✓ initMessagesでmessagesが初期化される (830ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        3.223s
Ran all test suites.
```

テストが成功しました！

## まとめなど
テスト環境構築の際、Babelのエラーで苦しみました。。

```
this.setDynamic is not a function
```
とか、

```console
Cannot find module '@babel/preset-env' 
```
とか。

Babelのバージョンに気をつけつつ、[GitHubのissue情報](https://github.com/babel/babel-loader/issues/560)や、[Babel公式ドキュメント](https://babeljs.io/docs/en/babel-preset-env)あたりにいろいろ情報ありますので、参考にしてください！

今回の記事では書かなかったですが、コンポーネント周りのテストをやろうとすると、やはりコンポーネントがテストしやすいIOの単位でまとまっていないと辛いです・・テストどう書けばいいの？ってなります。

やはり、TDDでテストを最初に書くことが大事ですね。