19
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-08-26

はじめに

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

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

基本的なテスト(htmlのタグが存在するなど)

以下のようなvueファイルがあったとき

index.vue
<template>
  <div>
    <label class="input-label">ラベル1</label>
    <input type="text" />
    <label class="input-label">ラベル2</label>
    <input type="password" />
  </div>
</template>

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

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

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

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

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>

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

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関数でラップ
  // 第2引数に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を渡しているとき

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

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

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

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とか)をしようしているとき

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>

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

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({})
  // テストで1回目に呼ばれたときのPromiseで返ってくる値を設定出来る、2回目以降はその値にならない
  jest.fn().mockResolvedValueOnce({})
  // 戻り値を設定できる
  jest.fn().mockReturnValue({})
  // テストで1回目に呼ばれたときの戻り値を設定出来る、2回目以降はその値にならない
  jest.fn().mockReturnValueOnce({})

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

  it('axiosコール時にパラメータを渡しているテスト', () => {
    wrapper.find('.axios').trigger('click')
    expect(axiosSpy).toHaveBeenCalledWith('https://google.com')
  });
})

おわりに

テスト書くのは大変だけど慣れれば楽しいね(´ー`)

19
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?