Jestでよく使うメソッドたち（主にvue-test-utils）

# はじめに
この１年クライアント側をTDDで開発してきて知識やノウハウが溜まってきたので備忘録として書いていきます。
以降随時更新予定です。（2020/08/26）

＊本記事はvue-test-utils ver1.Xに対応しています。アップデート後のver2.Xは仕様が変わっている部分がありますのでご注意ください。

## 基本的なテスト（htmlのタグが存在するなど）
以下のようなvueファイルがあったとき

```vue:index.vue
<template>
  <div>
    <label class="input-label">ラベル１</label>
    <input type="text" />
    <label class="input-label">ラベル２</label>
    <input type="password" />
  </div>
</template>
```
以下のようなテストが書ける

```javascript:index.spec.js
// テスト対象をインポート(インポートした変数はpascal caseにするらしい)
import Index from 'index.vue'
// vue-test-utilから必要なプロパティをインポート
import { shallowMount } from '@vue/test-utils'

describe('index.vue', () => {
  // テスト対象をshallowMount関数でラップ
  const wrapper = shallowMount(Index)

  it('inputタグが存在することのテスト', () => {
    // findの引数でselectorを指定、.exists()でboolean型の結果を取得、存在することなのでtoBeTrutyの関数を使って比較
    expect(wrapper.find('input[type="text"]').exists()).toBeTruthy()
  })

  it('labelタグが２つ存在することのテスト', () => {
    // findAllを使うと指定したselectoreを配列ですべて取得する
    // findAllの引数でselectorを指定、.length()でnumber型の結果を取得、２つ存在することなのでtoEqualの関数を使って比較
    expect(wrapper.findAll('.input-label').length).toEqual(2)
  }) 

  it('labelタグの１つ目が「ラベル１」と表示されているテスト', () => {
    // findAllで取得した配列は厳密にはWrapperArray型なので[0]ではアクセスできない
    // at(0)でアクセスできる
    expect(wrapper.findAll('.input-label').at(0).text()).toEqual('ラベル１')
  })
})
 
```

## vue独自のプロパティ(props・store・router・emitなど)を使うとき

```vue:index.vue
<template>
  <div>
    <div class="store">{{ hoge }}</div>
    <div class="props">{{ msg }}</div>
    <button class="next" @click="next">次へ進む</button>
  </div>
</template>
<script>
  props: {
    msg: String
  },
  data() {
    return {
      hoge: this.$store.state.hoge
    }
  },
  methods: {
    next() {
      const payload = { hoge: 'hoge' }
      this.$emit('hogeEmit', payload)
      this.$router.push('./hogehoge')
    }
  }
</script>
```

以下のようなテストが書ける

```javascript:index.spec.js
・・・
//上記と同じなので割愛


describe('index.vue', () => {
  // props、router、storeをスタブ化
  const routerSpy = {
    push: jest.fn()
  }
  const storeStub = {
    state: {
      hoge: 'hoge'
    }
  }
  const props = {
    msg : 'hoge message'
  }
  // テスト対象をshallowMount関数でラップ
  // 第２引数にpropsとrouterとstoreをスタブ化したものを渡す
  const wrapper = shallowMount(HelloWorld, {
    propsData: props,
    mocks: {
      $router: routerSpy,
      $store: storeStub
    }
  })

  it('divにstoreの値が表示されているテスト', () => {
    expect(wrapper.find('.store').text()).toEqual(storeStub.state.hoge)
  });

  it('divにpropsの値が表示されているテスト', () => {
    expect(wrapper.find('.props').text()).toEqual(props.msg)
  });

  it('次へ進むボタン押下すると親コンポーネントにイベント発火して./hogehogeに遷移するテスト', () => {
    const payload = { hoge: 'hoge'}
    // .triggerでclickイベント発火したようにふるまう
    wrapper.find('.next').trigger('click')
    const event = wrapper.emitted().hogeEmit

    expect(event[0][0]).toEqual(payload)
    expect(routerSpy.push).toHaveBeenCalledWith('./hogehoge')
  });
})
```

## 親コンポーネントから子コンポーネントにpropsを渡しているとき

```vue:index.vue
<template>
  <div>
    <Childcomponent hoge="hoge" />
  </div>
</template>

<script>
import Childcomponent from './child.vue'
export default {
  components: {
    Childcomponent
  }
}
</script>
```

以下のようなテストが書ける

```javascript:index.spec.js
・・・
// 上記と同じなので割愛

describe('index.vue', () => {
  // テスト対象をshallowMount関数でラップ
  const wrapper = shallowMount(HelloWorld)

  it('Childcomponentにpropsを渡しているテスト', () => {
    // shallowMountするとWrapper<Vue>型が返ってくる
    // そのプロパティに.vmというプロパティがあってそれ以下に$propsや$dataのプロパティがあるのでアクセスできる
    // 自作のコンポーネントをshallowMountすると「コンポーネント名-stub」の形式でマウントされる
    expect(wrapper.find('Childcomponent-stub').vm.$props.hoge).toEqual('hoge')
  });
})
```
## ライブラリ(axiosとか)をしようしているとき

```vue:index.vue
<template>
  <div>
    <button class="axios" @click="getParams">ボタン</button>
  </div>
</template>

<script>
import axios from 'axios'
export default {
  methods: {
    async getParams() {
      const params = await axios.get('https://google.com')
    }
  }
}
</script>
```

以下のようなテストが書ける

```javascript:index.spec.js
・・・
// 上記と同じなので割愛

// テスト対象で使用しているライブラリをインポート
import axios from 'axios'

describe('index.vue', () => {
  // mockImplementationで戻り値を設定できる
  const axiosSpy = jest.spyOn(axios, 'get').mockImplementation(() => {
    return Promise.resolve({})
  })

  /* mockImplementation以外にも以下のように書き方はいくつか存在する */
  // Promiseで返ってくる値を設定出来る
  jest.fn().mockResolvedValue({})
  // テストで１回目に呼ばれたときのPromiseで返ってくる値を設定出来る、2回目以降はその値にならない
  jest.fn().mockResolvedValueOnce({})
  // 戻り値を設定できる
  jest.fn().mockReturnValue({})
  // テストで１回目に呼ばれたときの戻り値を設定出来る、2回目以降はその値にならない
  jest.fn().mockReturnValueOnce({})

  // テスト対象をshallowMount関数でラップ
  // 第２引数にpropsとrouterとstoreをスタブ化したものを渡す
  const wrapper = shallowMount(HelloWorld)

  it('axiosコール時にパラメータを渡しているテスト', () => {
    wrapper.find('.axios').trigger('click')
    expect(axiosSpy).toHaveBeenCalledWith('https://google.com')
  });
})
```
# おわりに
テスト書くのは大変だけど慣れれば楽しいね(´ー｀)