axiosを利用したVue componentのUnitTest

本記事は [Vue.js Advent Calendar 2016](http://qiita.com/advent-calendar/2016/vue)の16日目の記事となります。よろしくお願いします。

## 始めに
先日「[Vue.js componentでvue-router,vue-resourceを利用したメソッドのUnitTestを書く方法](http://blog.hypermkt.jp/vue-js-component-unittesting/)」というのを書きましたが、vue-resourceは[Vue.js本家から引退した](https://jp.vuejs.org/2016/11/03/retiring-vue-resource/)こともあり、HTTP Clientをaxiosに乗り換えてみました。

今回はaxiosを利用したVue componentのUnit Testを書く方法をまとめます。また今回のサンプルコードは全て[こちら](https://github.com/hypermkt/hypermkt/tree/master/javascript/axios-unit-test)にあります。

## 環境
下記を利用します。テスト環境は Karma + mocha + chai + sinonですが、これらの説明は今回は省略します。

* [Vue.js 2.1.3](https://github.com/vuejs/vue)
* [axios 0.15.3](https://github.com/mzabriskie/axios)

## テストケース

### テスト対象コード
下記のようにaxios.getを利用し 仮のGETエントリポイント`/items` からアイテム一覧を取得する例とします。

```js
// test.vue
<script>
import axios from 'axios';
export default {
  data: () => {
    return {
      items: []
    }
  },
  methods: {
    fetchItems() {
      axios.get('/items').then((response) => {
        this.items = response.data;
      }).catch((response) => {
      });
    }
  }
}
</script>
```

上記のようなaxios.getは非同期処理となるため少し工夫が必要です。

### 準備
今回のテストを書くためには以下が必要となります。

* sinon
  * `axios.get` をスタブ化するために必要です
* bluebird
  * テストコード内で `Promise` オブジェクトを扱うために必要。これが無いと `Can't find variable: Promise.`と言われてしまう。

### ステップ１：サブクラスの作成
* テストに必要な各種ライブラリを読み込みます
* テスト対象となるコンポーネントをテストするにはインスタンス化させる必要があるので、 `Vue.extend` を利用してサブクラスを作成します。
  * [Vue.extend( options )](https://jp.vuejs.org/v2/api/#Vue-extend)

```js
import Vue from 'vue';
import _Test from '../../test.vue';
import sinon from 'sinon';
import axios from 'axios';
import Promise from 'bluebird';

const Test = Vue.extend(_Test);
```

### ステップ２：テストケースを作る
* 次にdescriptとitでテストケースを作成します。ここでのポイントは今回は非同期のテストなので仮引数に `done` を記述します。これはmochaの仕様で `done()` を実行することでmochaにテストが終了したことを通知します。
  * [ASYNCHRONOUS CODE](https://mochajs.org/#asynchronous-code)

```js
describe('Testコンポーネント', () => {
  it('fetchItemsでアイテム一覧が取得できる', (done) => {
    // ここにテストコードを書きます
  })
})
```

### ステップ３：axios.getのスタブ化
* まずは返り値を作成します。今回は成功ケースとするので `new Promise.resolve()` を変数に入れます。その際に引数としてレスポンスとして返す値を設定しておきます。
*  メソッドのスタブ化は `var stub = sinon.stub(object, "method", func);` の形式で出来ます。また `returns` で今回の返り値を設定します。
  * [Stub API](http://sinonjs.org/docs/#stubs-api)

```js
    let items = [
      { name: 'Apple' }
    ];

    let resolved = new Promise.resolve({
      data: items
    })

    let stub = sinon.stub(axios, 'get').returns(resolved)
```

### アサーション
* まずはステップ１で作成したサブクラスをインスタンス化しテスト対象メソッドを実行します。`axios.get` はスタブ化済みなのでAPIは実行されません。
* 先ほど作成した Promise オブジェクトの `.then` を利用してわざと成功ケースに入ります。この中でアサーションをします。
* そして最後に `done()` を実行することでmochaにテストが終了したことを通知して終了です。

```js
    const vm = new Test()
    vm.fetchItems();

    resolved.then(() => {
      expect(vm.items).to.be.eql(items)
      done()
    });
```



### テストコードの完成品は以下となります。

```js
import Vue from 'vue';
import _Test from '../../test.vue';
import sinon from 'sinon';
import axios from 'axios';
import Promise from 'bluebird';

const Test = Vue.extend(_Test);

describe('Testコンポーネント', () => {

  it('fetchItemsでアイテム一覧が取得できる', (done) => {
    let items = [
      { name: 'Apple' }
    ];

    let resolved = new Promise.resolve({
      data: items
    })

    let stub = sinon.stub(axios, 'get').returns(resolved)

    const vm = new Test()
    vm.fetchItems();

    resolved.then(() => {
      expect(vm.items).to.be.eql(items)
      done()
    }).catch(done);

  })
})
```

## 実行結果
以下は実行結果で正常に通ります。

```shell-session
# 中身はkarma start karma.conf.jsです
$ npm run test

> axios-unit-test@ test 
> karma start karma.conf.js

04 12 2016 19:25:43.713:INFO [framework.browserify]: bundle built
04 12 2016 19:25:43.725:INFO [karma]: Karma v1.3.0 server started at http://localhost:9876/
04 12 2016 19:25:43.726:INFO [launcher]: Launching browser PhantomJS with unlimited concurrency
04 12 2016 19:25:43.735:INFO [launcher]: Starting browser PhantomJS
04 12 2016 19:25:44.564:INFO [PhantomJS 2.1.1 (Mac OS X 0.0.0)]: Connected on socket /#G2ampAa8GMejBizyAAAA with id 33009121

  Testコンポーネント
    ✓ fetchItemsでアイテム一覧が取得できる

PhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 1 of 1 SUCCESS (0.012 secs / 0.012 secs)
TOTAL: 1 SUCCESS
```

## まとめ
後半になって気づいたんですが、書いている内容はほぼ[ブログに書いた記事](http://blog.hypermkt.jp/vue-js-component-unittesting/)と同じでした・・・。ただ前回と違うのは今回はaxiosであり、前回のようなグローバル関数ではないので調整方法が変わりました。これはこれで成功ケースのテストを通すのに数日かかりましたので、その成果としてまとめました。何らかの参考になれば幸いです。

明日は [@ponkotuyさん](http://qiita.com/ponkotuy) です。