Nuxt.jsの$router, $storeやcontextをjest+vue-test-utilsでモックする方法

# はじめに

nuxt.jsをJest+vue-test-utilsでテストする際に、nuxt.js特有のメソッド（`this.$router.push`など）や[context](https://nuxtjs.org/api/context)をモックするのに大変苦労しました。
他の人が同じ苦労をせずにすんなりテストを書く手助けになればいいな〜と思います！

# asyncDataやfetch以外でモックする方法

shallowMountやmountで用意されている、こちらの*mocks*というオプションを利用します。
https://vue-test-utils.vuejs.org/guides/#mocking-injections

## $storeを使った例

以下は`$store`のモックを作成した簡単な例です。

### Vue

```somePage.vue
<template>
  <div>
    <p v-if="isLoggedIn">
      ログインしています😊
    </p>
    <p v-if="!isLoggedIn">
      ログインしていません😢
    </p>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  computed: {
    ...mapGetters(['isLoggedIn'])
  }
}
</script>
```

### テスト

今回使っているのは`store`の`getters`内の`isLoggedIn`メソッドのみなので、

```js
const store = {
  getters: {
    isLoggedIn: jest.fn(() => true)
  }
}
```

↑みたいな感じで`store.getters.isLoggedIn`が機能するようなモックを作成して、`shallowMount`に渡していきます！

```somePage.spec.js
import { shallowMount } from '@vue/test-utils'
import SomePage from '~/pages/somePage'

describe('SomePage', () => {
  let wrapper, store

  beforeEach(() => {
    wrapper = shallowMount(SomePage, {
      mocks: { $store: store }
    })
  })

  describe('when logged in', () => {
    beforeAll(() => {
      store = {
        getters: {
          isLoggedIn: jest.fn(() => true)
        }
      }
    })

    it('shows logged in text', () => {
      expect(wrapper.text()).toContain('ログインしています😊')
    })
  })
})
```

## $routerを使った例

### Vue

```somePage.vue
<template>
  <div class="home" @click="pushToHome()">
    ホーム
  </div>
</template>

<script>
export default {
  methods: {
    pushToHome() {
      this.$router.push('/')
    }
  }
}
</script>
```

「nuxt-link使えよ！！！」っていう話なんですが、例のためなので今回は許してください。笑

### テスト

今回使っているのは`$router.push`というメソッドなので、`$store`をモックしたときと同様、

```js
const router = { push: jest.fn() }
```

↑こんな感じで`router.push`が機能するようなモックを`shallowMount`に渡します。

```somePage.spec.js
import { shallowMount } from '@vue/test-utils'
import SomePage from '~/pages/somePage'

describe('SomePage', () => {
  let wrapper
  const router = { push: jest.fn() }

  beforeEach(() => {
    wrapper = shallowMount(SomePage, {
      mocks: { $router: router }
    })
  })

  describe('when home is clicked', () => {
    beforeEach(() => {
      const home = wrapper.find('.home')
      home.trigger('click')
    })

    it('pushes to home', () => {
      expect(router.push).toBeCalledWith('/')
    })
  })
})
```

# asyncDataやfetched内でcontextをモックする方法

asyncDataとfetchedはnuxtが用意してくれている関数なので、vue-test-utilsのmountを使ってテストをしようとしても、asyncDataは実行されません。
なので、こちらから手動で実行させる必要があります。

## asyncDataでstoreを使った例

### Vue

```somePage.vue
<template>
  <div>
    <p v-if="isLoggedIn">
      ログインしています😊
    </p>
    <p v-if="!isLoggedIn">
      ログインしていません😢
    </p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isLoggedIn: false
    }
  },

  asyncData({ store }) {
    return { isLoggedIn: store.getters.isLoggedIn }
  }
}
</script>
```

### テスト

「asyncDataやfetch以外でモックする方法」でもやったように、`store.getters.isLoggedIn`が機能するような`store`のモックを作ります。
ただ、今回は`shallowMount`の`mocks`に渡すのではなく、`asyncData`を呼び出す際に直接渡します。

```somePage.spec.js
import { shallowMount } from '@vue/test-utils'
import SomePage from '~/pages/somePage'

describe('SomePage', () => {
  let wrapper, store

  beforeEach(() => {
    wrapper = shallowMount(SomePage)
    // 1. asyncDataを呼び出す
    const data = wrapper.vm.$options.asyncData({ store })
    // 2. asyncDataが返す値をVue instanceのデータに設定する
    wrapper.setData(data)
  })

  describe('when logged in', () => {
    beforeAll(() => {
      store = {
        getters: {
          isLoggedIn: jest.fn(() => true)
        }
      }
    })

    it('shows logged in text', () => {
      expect(wrapper.text()).toContain('ログインしています😊')
    })
  })
})
```

`store`以外にも、`asyncData`内で`app`や`error`などを使う場合も、この例と同様にモックできます。

# 最後に

以上を使えば、nuxt特有のメソッドを簡単にモックできるはずです！
プラグインのモックの方法も、需要があれば後々記事を書くかもしれません。
筆者はまだnuxtを使い始めたばかりなので、間違いやよりよい方法があればガシガシご指摘ください！