Vuexの状態を包括的にテストする

## やること
- vue + vuexで入力フォームを構築する
- 入力、バリデーションなどの状態をすべてvuexに持たせる
- 正しい入力、誤った入力などの状態を起こしてテストする

## ポイント
関数単位のテストではなく、連続的に状態を変更させてテストする

## 使用環境
- vue-cliのwebpackテンプレート
- vue + vuex
- 見やすくするためBootstrapを使用
- デフォルトのテスト環境を使用する
- karma + mocha + chai

## Demo
[https://renowan.github.io/qiita-vuex-test/](https://renowan.github.io/qiita-vuex-test/)

## フォーム仕様
- 入力項目name（名前）, age（年齢）
- 両方とも入力必須
- 名前は4文字以上
- 年齢は数字のみ、かつ0以上
- 名前、年齢入力済み、エラーなしの場合、Submitが押せるようになる（disabled = false）
- Clearを押したらデータが初期化される

## Storeの構造
```javascript
// actions
const actions = {
  // 入力
  input ({ commit, state }, data) {
    let value = data.value
    // name
    if (data.key === 'name') {
      commit(types.INPUT_NAME, value)
    }
    // age
    if (data.key === 'age') {
      // 数字なら数字に変換する
      if (Number.isInteger(Number(value))) {
        value = Number(value)
      }
      commit(types.INPUT_AGE, value)
    }
  }
}

// mutations
const mutations = {
  [types.INIT] (state) {
    state.name = ''
    state.age = null
    state.disabled = true
  },
  [types.INPUT_NAME] (state, data) {
    state.name = data
  },
  [types.INPUT_AGE] (state, data) {
    state.age = data
  }
}

// getters
const getters = {
  app: state => state,
  // nameが未入力、または4文字未満
  isNameError: state => {
    const name = state.name
    return name === '' || name.length < 4
  },
  // ageは数字以外、または1未満
  isAgeError: state => {
    const age = state.age
    return !(Number.isInteger(age) && age > 0)
  },
  // name と ageどっちがエラー
  disabled: (state, getters) => {
    return getters.isNameError || getters.isAgeError
  }
}
```

## テスト開始
まずはstoreのimport

```javascript
import store from '@/store/'
```

必要なものを宣言しておく

```javascript
const app = store.state.app
const dispatch = store.dispatch
const commit = store.commit
const getters = store.getters
```

## 初期状態の確認
```javascript
describe('初期状態テスト', () => {
  it('stateの初期状態', () => {
    expect(app.name).to.equal('')
    expect(app.age).to.equal(null)
  })
  it('gettersの初期状態', () => {
    expect(getters.isNameError).to.equal(true)
    expect(getters.isAgeError).to.equal(true)
    expect(getters.disabled).to.equal(true)
  })
})
```

## 入力してみよう
```javascript
describe('入力テスト', () => {
  it('nameを入力', () => {
    dispatch('input', {key: 'name', value: 'myname'})
    expect(app.name).to.equal('myname')
  })
  it('ageを入力', () => {
    dispatch('input', {key: 'age', value: 3})
    expect(app.age).to.equal(3)
  })
})
```

## バリデーションチェック
正しく入力したのでエラーはない。submitも押せるはず

```javascript
it('バリデーションテストエラーが起きない、submitボタンが押せる', () => {
  expect(getters.isNameError).to.equal(false)
  expect(getters.isAgeError).to.equal(false)
  expect(getters.disabled).to.equal(false)
})
```

## ちゃんとエラーチェックできているか？
```javascript
it('ageを0と入力してageエラーを起こす', () => {
  dispatch('input', {key: 'age', value: 0})
  expect(getters.isAgeError).to.equal(true)
})
it('nameをabcと入力して文字数が足りず、エラーになる', () => {
  dispatch('input', {key: 'name', value: 'abc'})
  expect(getters.isNameError).to.equal(true)
})
```

## 入力を修正して再度エラーなしに戻る
```javascript
it('nameとageをエラーが起きないように入力する、ボタンが押せるようになる', () => {
  dispatch('input', {key: 'name', value: 'abcdef'})
  dispatch('input', {key: 'age', value: 4})
  expect(getters.isNameError).to.equal(false)
  expect(getters.isAgeError).to.equal(false)
  expect(getters.disabled).to.equal(false)
})
```

## Clearボタンを押せて初期化する
```javascript
it('Clearを押して初期化する->またエラーになる', () => {
  commit('INIT')
  expect(getters.isNameError).to.equal(true)
  expect(getters.isAgeError).to.equal(true)
  expect(getters.disabled).to.equal(true)
})
```

値がすべてデフォルトに戻されたことを確認できた

## 完成
いかがでしょうか？ このように状態をすべてstoreに持たせて、いろんなシチュエーションを起こしてテストをかければアプリの動作はしっかり担保できる。ロジックはすべてstoreにあるので、表示用のコンポーネントのテストは最低限に抑えられて効率がよくなると思う。

### リポジトリ
https://github.com/renowan/qiita-vuex-test