Open9

Mock Service Worker(msw)をNuxt.jsでモックサーバーとして使ってみる

Yoshiyuki HisamatsuYoshiyuki Hisamatsu

ブラウザでモック用の API として使う場合

mocks/browser.js

import { setupWorker } from 'msw'
import { handlers } from './handlers'

export const worker = setupWorker(...handlers)

mocks/handlers.js

import { rest } from 'msw'

export const handlers = [
  rest.get('/users', (req, res, ctx) => {
    return res(
      ctx.status(200),
      ctx.json([
        {
          id: 1,
          name: 'foo',
        },
        {
          id: 2,
          name: 'bar',
        }
      ]),
    )
  }),
]

https://zenn.dev/ryo_kawamata/articles/mock-api-server-with-msw

Yoshiyuki HisamatsuYoshiyuki Hisamatsu

Nuxt の plugin から msw を呼び出す

plugins/mock.ts

export default () => {
  if (process.env.NODE_ENV === 'development') {
    const { worker } = require('../mocks/browser')
    worker.start()
    console.log('worker started')
  }
};

nuxt.config.ts

  plugins: [
    { src: '@/plugins/mock', mode: 'client' }
  ],

これで、 SPA モード、且つ開発環境のみでモックサーバーが起動する。

Yoshiyuki HisamatsuYoshiyuki Hisamatsu

page にサンプルページを追加する

page/mock-sample.vue

<template>
  <div class="container">
    <div>
      <h1 class="title">モックサンプル画面</h1>
      <p>{{ value1 }}</p>
      <p>{{ value2 }}</p>
    </div>
  </div>
</template>

<script lang="ts">
import Vue from 'vue';
import { mapActions } from 'vuex'

export type DataType = {
  value1: string
};

// via https://qiita.com/is_ryo/items/6fc799ba4214db61d8ab
export default Vue.extend({
  data(): DataType {
    return {
      value1: 'initial value',
    }
  },
  // data をリアクティブに更新するならこちらを使う
  async asyncData({ $axios }) {
    // この /users がモックサーバーを見に行く
    const { data } = await $axios.get(`/users`)

    return {
      value1: data[0].name
    }
  },
  // Vuex を使うならこちらを使う
  async fetch({ store }) {
    await store.dispatch('fetchApi')
  },
  computed: {
    value2() {
      return this.$store.state.result[1].name
    }
  },
  mounted(): void {
    console.log('モック mounted')
  },
  created(): void {
    console.log('モック created')
    // asyncData や fetch はいずれ廃止されるので、なるべく使わないほうがよいかもしれない
    //this.fetchApi()
  },
  methods: {
    ...mapActions({
      fetchApi: 'fetchApi'
    })
  },
});
</script>

<style>
.container {
  margin: 0 auto;
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
}

.title {
  font-family: 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont,
    'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  display: block;
  font-weight: 300;
  font-size: 50px;
  color: #35495e;
  letter-spacing: 1px;
}
</style>
Yoshiyuki HisamatsuYoshiyuki Hisamatsu

store も追加していく

nuxt.config.ts に axios の module を追加しておく

これをしておかないと this.$axios.get のように使うことができないため。

  // Modules: https://go.nuxtjs.dev/config-modules
  modules: [
    '@nuxtjs/axios',
  ],

  axios: {
    // See https://github.com/nuxt-community/axios-module#options
  },

tsconfig.json にも追加しておかないと型で怒られる

    "types": [
      "@nuxt/types",
      "@types/node",
      "@nuxtjs/axios",
      "@types/jest"
    ]

store/index.ts

import Vue from 'vue'
import { AxiosRequestConfig } from 'axios'

/**
 * レスポンス json オブジェクト用インターフェイス
 */
export interface ApiInterface {
  id: string
  name: string
}

/**
 * store 用インターフェイス
 */
export interface StateInterface {
  result: (ApiInterface)[]
}

/**
 * state
 */
export const state = (): StateInterface => ({
  result: [{ id: 'initial id', name: 'initial name' }]
})

/**
 * getters
 */
export const getters = {
  getResult(state: StateInterface): ApiInterface | undefined {
    if (!state.result || state.result.length <= 0) {
      return
    }

    return state.result[0]
  }
}

/**
 * mutations
 */
export const mutations = {
  saveApiResult(state: StateInterface, apiResult: ApiInterface[]): void {
    // オブジェクトの key 値の value を変更する場合、 Vue 側に通知がいかないので
    // Vue.set 経由で渡す
    // Vue.set(state.result, 'key', apiResult)

    // 既存の result オブジェクトに追加する場合
    // state.result = {
    //   ...state.result,
    //   apiResult
    // }

    // オブジェクトインスタンスをまるっと新しくして result に追加する場合
    // state.result = Object.assign({}, state.result, {
    //   age: 27,
    //   favoriteColor: 'Vue Green'
    // })

    // 配列をそのまま result に入れる場合
    state.result = apiResult
  }
}

/**
 * actions
 */
export const actions = {
  async fetchApi(
    this: Vue,
    // @ts-ignore
    { state, commit }: any,
  ): Promise<void> {
    try {
      // @ts-ignore
      const { data }: any = await this.$axios.get(
        '/users',
        {
        } as AxiosRequestConfig // https://github.com/axios/axios/blob/master/index.d.ts
      )
      console.log('apiResult:', data)

      // 値をストアに保存
      commit('saveApiResult', data)
    } catch (err) {
      console.log(err)
    }
  }
}