Vue.js用テストライブラリ「avoriaz」入門

## 背景
Vue.js本家からは公式のテストライブラリは提供されておらず、非公式で[vue\-test](https://github.com/callumacrae/vue-test), [avoriaz](https://github.com/eddyerburgh/avoriaz), [vue\-testing](https://github.com/BosNaufal/vue-testing), [revue](https://github.com/codebryo/revue), [vue-unit](https://github.com/wrseward/vue-unit)等が公開されています。それらの中で一番Star数が多いavoriazついて調査した内容をまとめます。

## 前提
* Karma + Mocha + Chaiを利用する
* 以下のHelloコンポーネントをテスト対象とする

```html:Hello.vue
<template>
  <div id="hello">
    <img src="http://vuejs.org/images/logo.png">
    <h1>{{ msg }}</h1>
    <h2 class="subtitle">Essential Links</h2>
    <h2>Ecosystem</h2>
    <h3>nickname: {{ user.nickname }}</h3>
  </div>
</template>

<script>
export default {
  name: 'hello',
  props: ['user'],
  data () {
    return {
      msg: 'Welcome to Your Vue.js App'
    }
  },
  methods: {
    hello: () => {
      return 'world'
    }
  }
}
</script>

<style scoped>
#hello {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}

.subtitle {
  font-size: 16px;
}

</style>
```

## 準備
###  インストール
```shell-session
$ npm install --save-dev avoriaz
```

## API
### mount
* レンダリングされたVueコンポーネントのラッパーオブジェクトを返します。
* 第2引数は任意で、[Vue API](https://vuejs.org/v2/api/)に沿った`store`, `propsData`などを渡すことが出来ます。

```js:Hello.spec.js
import Hello from '../../src/components/Hello.vue'
import { mount } from 'avoriaz'

describe('Hello.vue', () => {
  it('mount', () => {
    const wrapper = mount(Hello, {
      propsData: {
        user: {nickname: 'John'}
      }
    })
  })
})
```

#### contains
指定のセレクターが含まれているか検証できます。

```js
  it('contains', () => {
    const wrapper = mount(Hello, {
      propsData: {
        user: {nickname: 'John'}
      }
    })
    expect(wrapper.contains('img')).to.be.eql(true)
    expect(wrapper.contains('h1')).to.be.eql(true)
    expect(wrapper.contains('h2')).to.be.eql(true)
  })
```

#### data
`data`は、Vueインスタンスのdataオブジェクトを返します。dataオブジェクト内に、指定の値が入っているか検証できます。

```js
  it('data', () => {
    const wrapper = mount(Hello, {
      propsData: {
        user: {nickname: 'John'}
      }
    })
    let msg = 'Hello World'
    wrapper.setData({msg: msg})
    expect(wrapper.find('h1')[0].text()).to.be.eql(msg)
    expect(wrapper.data().msg).to.be.eql(msg)
  })
```

#### find
セレクターで指定したDOMノードかVueコンポーネントの配列が返ります。指定したエレメントが存在するか検証できます。

```js
  it('find', () => {
    const wrapper = mount(Hello, {
      propsData: {
        user: {nickname: 'John'}
      }
    })
    expect(wrapper.find('h2').length).to.be.eql(2)
    expect(wrapper.find('h2')[0].is('h2')).to.be.eql(true)
  }) 
```

#### hasAttribute
指定した属性が存在するか検証できます。

```js
  it('hasAttribute', () => {
    const wrapper = mount(Hello, {
      propsData: {
        user: {nickname: 'John'}
      }
    })
    expect(wrapper.hasAttribute('id', 'hello')).to.be.eql(true)
  })
```

#### instance
Vueコンポーネントのインスタンスオブジェクトを返します。このオブジェクトを通じて、methodsやdataオブジェクトにアクセスできます。

```js
  it('instance', () => {
    const wrapper = mount(Hello, {
      propsData: {
        user: {nickname: 'John'}
      }
    })
    expect(wrapper.instance().hello()).to.be.eql('world')
    expect(wrapper.instance().msg).to.be.eql('Welcome to Your Vue.js App')
  })
```


#### isVueComponent
Vueコンポーネントかどうか検証できます。（使うのか・・？）

```js
  it('isVueComponent', () => {
    const wrapper = mount(Hello, {
      propsData: {
        user: {nickname: 'John'}
      }
    })
    expect(wrapper.isVueComponent).to.be.eql(true)
  })
```

#### methods
Vueオブジェクトのメソッドにアクセスできます。

```js
  it('methods', () => {
    const wrapper = mount(Hello, {
      propsData: {
        user: {nickname: 'John'}
      }
    })
    expect(wrapper.methods().hello()).to.be.eql('world')
  })
```

但しmethods()を利用すると、以下のWarningが表示され`vm`経由で呼び出すことが案内されています。

```shell-session
warning: functions returned by methods() will not have this bound to the vue instance. Calling a method that uses this will result in an error. You can access methods by using the vue instance. e.g. to call a method function named aMethod, call wrapper.vm.aMethod(). See https://github.com/eddyerburgh/avoriaz/issues/15
```

#### name
コンポーネント名の検証ができます。(使うかな・・・？）

```js
  it('name', () => {
    const wrapper = mount(Hello, {
      propsData: {
        user: {nickname: 'John'}
      }
    })
    expect(wrapper.name()).to.be.eql('hello')
  })
```

#### propsData
propDataのオブジェクトを返します。

```js
  it('propsData', () => {
    const wrapper = mount(Hello, {
      propsData: {
        user: {nickname: 'John'}
      }
    })
    expect(wrapper.propsData().user.nickname).to.be.eql('John')
  })
```

但しpropsData()を利用すると、以下のWarningが表示され`vm`経由で呼び出すことが案内されています。

```shell-session
warning: functions returned by propsData() will not have this bound to the vue instance. Calling a propsData function that uses this will result in an error. You can access propsData functions by using the vue instance. e.g. to call a method function named propsDataFunc, call wrapper.vm.$props.propsDataFunc(). See https://github.com/eddyerburgh/avoriaz/issues/15
```

#### text
指定オブジェクトの文字列を返します。

```js
  it('text', () => {
    const wrapper = mount(Hello, {
      propsData: {
        user: {nickname: 'John'}
      }
    })
    expect(wrapper.find('h1')[0].text()).to.be.eql('Welcome to Your Vue.js App')
  })
```

#### vm
`vm`プロパティは、Vueコンポーネントのオブジェクトを保持しています。それを通じて、data、methodsオブジェクトなどにアクセスできます。

```js
  it('vm', () => {
    const wrapper = mount(Hello, {
      propsData: {
        user: {nickname: 'John'}
      }
    })
    expect(wrapper.vm.user.nickname).to.be.eql('John')
  })
```

### shallow
全ての子コンポーネントをスタブして、レンダリングしたラッパーオブジェクトを返します。子コンポーネントは `<!---->` というコメントアウト文字列に置換されます。

```js
  describe('shallow', () => {
    const wrapper = shallow(Hello, {
      propsData: {
        user: {nickname: 'John'}
      }
    })
    expect(wrapper.isVueComponent).to.be.eql(true)
  })
```

### selectors
多数のavoriazメソッドは、セレクターを引数に取ります。

#### タグセレクター
HTMLタグで指定します。またタグ指定はjQueryのように、子孫要素や子要素指定も可能です。

```js
    it('tag selectors', () => {
      const wrapper = mount(Hello, {
        propsData: {
          user: {nickname: 'John'}
        }
      })

      // タグを指定する
      const div = wrapper.find('div')[0]
      expect(div.is('div')).to.be.eql(true)

      // 子孫要素指定
      const img1 = wrapper.find('div img')[0]
      expect(img1.is('img')).to.be.eql(true)

      // 子要素指定
      const img2 = wrapper.find('div > img')[0]
      expect(img2.is('img')).to.be.eql(true)
    })
```

#### idセレクター
id属性値で指定できます。

```js
    it('id selectors', () => {
      const wrapper = mount(Hello, {
        propsData: {
          user: {nickname: 'John'}
        }
      })

      const hello = wrapper.find('#hello')[0]
      expect(hello.is('div')).to.be.eql(true)
    })
```

#### classセレクター
class属性値で指定できます。

```js
    it('class selectors', () => {
      const wrapper = mount(Hello, {
        propsData: {
          user: {nickname: 'John'}
        }
      })

      const subtitle = wrapper.find('.subtitle')[0]
      expect(subtitle.is('h2')).to.be.eql(true)
    })
```

#### 属性セレクター
jQuery同様に`[]`内に属性と値をイコールで挟んで記述することで、属性指定できます。

```js
    it('attribute selectors', () => {
      const wrapper = mount(Hello, {
        propsData: {
          user: {nickname: 'John'}
        }
      })

      const src1 = wrapper.find('[src]')[0]
      expect(src1.is('img')).to.be.eql(true)

      const src2 = wrapper.find('[src="http://vuejs.org/images/logo.png"]')[0]
      expect(src2.is('img')).to.be.eql(true)

      // ダブルコーテーション囲みでも問題ない
      const src3 = wrapper.find("[src='http://vuejs.org/images/logo.png']")[0]
      expect(src3.is('img')).to.be.eql(true)
    })
```

## まとめ
avoriazはドキュメント、サンプルコード共に充実しており、APIもとてもシンプルでした。[Karma + Mocha](https://github.com/eddyerburgh/avoriaz-karma-mocha-example), [Karma + Jasmine](https://github.com/eddyerburgh/avoriaz-karma-jasmine-example)など各種テストライブラリの組み合わせのサンプルコードも公開されており、最初の導入の敷居はとても低い印象です。avoriazのようなフレームワーク専用のテストライブラリを利用することで、テストコードが書きやすくなので、コンポーネントのユニットテストを導入したい場合には利用すべきだと思いました。

## 参考
* [eddyerburgh/avoriaz\-karma\-mocha\-example: Example using avoriaz with karma and mocha to test Vue\.js components](https://github.com/eddyerburgh/avoriaz-karma-mocha-example)
* 本件で扱うサンプルコードは[こちら](https://github.com/hypermkt/hypermkt/tree/master/javascript/vuejs-avoriaz002)にアップしました。