nuxt.js の Unit Test のツラミについて

これ、みんなどうやっているのか聞きたいので投稿してみる。
もっといい方法が存在するのであれば教えてください。

---

nuxt.js は SSR によるアプリケーション開発における必要なものが大体揃っており、良いなーということで使い始めてはいるのですが、やはり、まだまだRC版ということで課題もあります。今回は Unit テストで詰まりました。

nuxt.js では、`nuxt-build` により store をまとめたり、nuxt.js がプリセットで用意している component を `.nuxt/` ディレクトリに用用意する。さらに面倒なことに、`.nuxt` で展開されているものは、webpackで使う前提となっています。 内部的には、 `require.context` を利用しているためです。

nuxt.js 公式では、 [ava による e2e テストの方法について公式で述べている](https://nuxtjs.org/guide/development-tools#end-to-end-testing)ものの、Unitテストについてはイマイチ情報が少ないです。 [Unit testing Issue #461 nuxt/nuxt.js](https://github.com/nuxt/nuxt.js/issues/461) あたりも参照のこと。

// 上記の e2e のテストについても毎回 build が走ってしまうので、個人的には気に食わない。

ということで、自分なりにテスト方法について模索してみました。

## 前提

ここでは、`vue-init webpack` によりデフォルトでオススメされる、[jest](https://facebook.github.io/jest/) をテストツールとして使います。

## 導入

yarn で必要なものを入れていく

```
yarn add -D babel-core babel-jest babel-plugin-dynamic-import-node \
           babel-plugin-transform-es2015-modules-commonjs \
           babel-plugin-transform-runtime babel-preset-env \
           babel-preset-stage-2 babel-register jest jest-serializer-vue vue-jest
```

必要なファイルを変更したり、作っていく作業を行う

```json:.babelrc
{
  "presets": [
    ["env", {
      "modules": false
    }],
    "stage-2"
  ],
  "plugins": ["transform-runtime"],
  "env": {
    "test": {
      "presets": ["env", "stage-2"],
      "plugins": ["transform-es2015-modules-commonjs", "dynamic-import-node"]
    }
  }
}
```

```js:.eslintrc.js
module.exports = {
  env: {
    jest: true // jest を true にする
  }
}
```

```js:test/unit/jest.conf.js
const path = require('path')

module.exports = {
  rootDir: path.resolve(__dirname, '../../'),
  moduleFileExtensions: [
    'js',
    'json',
    'vue'
  ],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/$1'
  },
  transform: {
    '^.+\\.js$': '<rootDir>/node_modules/babel-jest',
    '.*\\.(vue)$': '<rootDir>/node_modules/vue-jest'
  },
  snapshotSerializers: ['<rootDir>/node_modules/jest-serializer-vue'],
  setupFiles: ['<rootDir>/test/unit/setup'],
  mapCoverage: true,
  coverageDirectory: '<rootDir>/test/unit/coverage',
  collectCoverageFrom: [
    'components/**/*.{js,vue}'
  ]
}
```

```js:test/unit/setup.js
import Vue from 'vue'
Vue.config.productionTip = false
```

## テスト記述

```js:test/unit/specs/components/AppHeader.spec.js
import Vue from 'vue'
import Vuex from 'vuex'
import VueRouter from 'vue-router'
import VueI18n from 'vue-i18n'
import AppHeader from '@/components/AppHeader.vue'

const store = require('@/store/')

Vue.use(VueRouter)
Vue.use(Vuex)
Vue.use(VueI18n)

describe('AppHeader.vue', () => {
  it('pass', () => {
    AppHeader.components = {}

    // nuxt-link component を使っている場合は、モックを載せる
    // この中身は、.nuxt/components/nuxt-link.js にも出来ているので、
    // ビルドが行われている前提であれば、そちらを呼んでも良い。
    AppHeader.components.NuxtLink = {
      name: 'nuxt-link',
      functional: true,
      render (h, {data, children}) {
        return h('router-link', data, children)
      }
    }

    const Constructor = Vue.extend(AppHeader)

    // VueI18n を使っているので、載せる。
    // 但しUnitレベルでは別にデータは不要なので、silentTranslationWarn: true にして警告を消す
    const vm = new Constructor({router: new VueRouter(), i18n: new VueI18n({silentTranslationWarn: true})})

    // store/index.js (Module Mode) から store を作る作業
    vm.$store = new Vuex.Store(Object.assign(store, {
      state: store.state()
    }))
    vm.$mount()

    // テストを記述する
  })
})
```

## テスト実行

`yarn run` から実行できるようにする

```json:package.json
{
  "scripts": {
    "unit": "jest --config test/unit/jest.conf.js "
  }
}
```

```
yarn run unit
```

## 悩ましいポイント

- 今のところ、ビルド済みの `.vue/` に依存しない形で書いているが、依存させたほうが良いかな？
- Vuex モジュールが増えてくると面倒なことになりそう。
- *.vue ファイルのカバレッジが、何故かちゃんと取得できない場合もある。