jestを活用したNuxt.jsのコンポーネントのテスト

この記事は[Nuxt.js #2 Advent Calendar 2018](https://qiita.com/advent-calendar/2018/nuxt-js-2)の6日目の記事です。
Nuxt歴はまだ5ヶ月程度と浅いのですが、自分の勉強も兼ねて書いてみました。

# 前の記事について
本題に入る前に、カレンダーの4日目を担当された`@kai_kou`さんの記事を以下に貼っておきます。
[Nuxt.js+TypeScriptなアプリをProgressive Web App(PWA)化する](https://qiita.com/kai_kou/items/f7e7925d0f0eee447c85)

`nuxt-serverless`という、nuxtをAWSのLambda,API Gateway,S3上にホスティングするライブラリのpwa化のノウハウが記された記事です。今年、iPhoneのSafariが対応したこともありPWAは今後重要なテクノロジーになる可能性があると思われます。ぜひともご一読ください。

# コンポーネントのテストを書く意義
Nuxtではボタン等の画面の部品をコンポーネント化することで、再利用性を高めることができます。
コンポーネントのテストを書くことで、利用者に対してその仕様を効率的に伝えることができます。

# 準備
Nuxt.jsの環境は構築済みの前提です。

## 必要なパッケージのインストール
```
npm i -D jest jest-vue-preprocessor babel-jest vue-test-utils
```

## package.jsonの編集

以下の内容を追記

```
  "jest": {
    "moduleNameMapper": {
      "^vue$": "vue/dist/vue.common.js"
    },
    "moduleFileExtensions": [
      "js",
      "vue"
    ],
    "transform": {
      "^.+\\.js$": "<rootDir>/node_modules/babel-jest",
      ".*\\.(vue)$": "<rootDir>/node_modules/jest-vue-preprocessor"
    }
  }
```

また`scripts`に以下を追加

```
    "test": "jest"
```

# コンポーネントのテスト
今回は簡単なカウンターコンポーネントを作成するものとします。

- 初期値は0
- ボタンをクリックすると値をインクリメントする

このコンポーネントのテストコードを書きます。

```Counter.spec.js
import { mount } from '@vue/test-utils'
import Counter from './Counter.vue'

describe('Counter.vue', () => {
  let wrapper
  beforeEach(() => {
    wrapper = mount(Counter)
  })
  test('Setup correctly', () => {
    expect(true).toBe(true)
  })
  test('increments the counter value when button is clicked', () => {
    expect(wrapper.text()).toContain('Counter: 0')
    wrapper.find('button.inclement-button').trigger('click')
    expect(wrapper.text()).toContain('Counter: 1')
  })
})
```

```Counter.vue
<template>
  <div>
    <div>Counter: {{ counter }}</div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      counter: 0
    }
  }
}
</script>
```

# テスト結果
まずは`inclement`のテストが失敗することを確認します。
※以下のコンソールはホスト名など一部変更してます。

```
$ npm test

> myproject@1.0.0 test /Documents/myproject
> jest

 FAIL  test/Counter.spec.js
  Counter.vue
    ✓ Setup correctly (16ms)
    ✕ increments the counter value when button is clicked (19ms)

  ● Counter.vue › increments the counter value when button is clicked

    [vue-test-utils]: find did not return button.inclement-button, cannot call trigger() on empty Wrapper

      12 |   test('increments the counter value when button is clicked', () => {
      13 |     expect(wrapper.text()).toContain('Counter: 0')
    > 14 |     wrapper.find('button.inclement-button').trigger('click')
         |                                             ^
      15 |     expect(wrapper.text()).toContain('Counter: 1')
      16 |   })
      17 | })

      at throwError (node_modules/@vue/test-utils/dist/vue-test-utils.js:51:9)
      at ErrorWrapper.trigger (node_modules/@vue/test-utils/dist/vue-test-utils.js:888:3)
      at Object.trigger (test/Counter.spec.js:14:45)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 passed, 2 total
Snapshots:   0 total
Time:        2.304s
Ran all test suites.
npm ERR! Test failed.  See above for more details.

```

想定通り、実装していない箇所のテストがfailしました。

# テスト対象のコンポーネントの開発
テストがfailした部分を実装します。

```Counter.vue
<template>
  <div>
    <div>Counter: {{ counter }}</div>
    <button
      class="inclement-button"
      @click="inclement">Increment</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      counter: 0
    }
  },
  methods: {
    inclement() {
      this.counter++
    }
  }
}
</script>
```

# テスト結果
想定通り、テストが通りました。

```
$ npm test

> myproject@1.0.0 test /Documents/myproject
> jest

 PASS  test/Counter.spec.js
  Counter.vue
    ✓ Setup correctly (19ms)
    ✓ increments the counter value when button is clicked (17ms)

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

# 総括
jestを活用することで、NuxtのコンポーネントをTDDに則って開発することができました。
実際の開発ではどうしても後回しになりがちなコンポーネントの単体テストですが、これだけ簡単に書けるのであれば積極的に導入を進めても恩恵があるのではないでしょうか。