Open6

Mock Server Worker + vitest + happy-dom で proxy された相対URLをハンドルする方法

Coji MizoguchiCoji Mizoguchi

Mock Server Worker + vitest + happy-dom で API のモックしたユニットテストをしたい。
API は front の稼働してる URL の一部が backend に reverse proxy されているという特殊環境。
普通にやると以下のように実行時エラーがでる。

TypeError: Only absolute URLs are supported
 ❯ getNodeRequestOptions node_modules/node-fetch/lib/index.js:1327:9
 ❯ node_modules/node-fetch/lib/index.js:1440:19
 ❯ fetch node_modules/node-fetch/lib/index.js:1437:9
 ❯ node_modules/happy-dom/lib/window/Window.js:557:25
 ❯ Window.<anonymous> node_modules/happy-dom/lib/window/Window.js:548:39
 ❯ step node_modules/happy-dom/lib/window/Window.js:67:23
 ❯ Object.next node_modules/happy-dom/lib/window/Window.js:48:53
 ❯ node_modules/happy-dom/lib/window/Window.js:42:71
Coji MizoguchiCoji Mizoguchi

エラーが出るケース

モックハンドラ

import { rest } from 'msw'

export const handlers = [
  rest.get('/hello.txt', (_req, res, ctx) => {
    return res(ctx.status(200), ctx.text('Mock!'))
  }),
]

テスト対象コンポーネント

<script setup lang="ts">
import { onMounted, ref } from 'vue'

const test = ref('')
onMounted(
  async () =>
    (test.value = await fetch('/hello.txt')
      .then(async (res) => {
        return await res.text()
      })
      .catch((e) => {
        console.log('fetch error: ', e)
        return 'error!'
      }))
)
</script>

<template>
  <div>Hello {{ test }}</div>
</template>

テストコード

import { render, waitFor, screen } from '@testing-library/vue'
import { server } from '../src/mocks/server'
import App from '../src/App.vue'

describe('describe', async () => {
  beforeAll(() => {
    server.listen()
  })
  afterEach(() => {
    server.resetHandlers()
  })
  afterAll(() => {
    server.close()
  })

  it('test1', async () => {
    const wrapper = render(App)
    expect(await wrapper.findAllByText('Hello Mock!')).toBeTruthy()
  })
})
Coji MizoguchiCoji Mizoguchi

原因は happy-dom が使ってる node-fetch が absolute URL しか対応してない(node用なので)中で、相対URLを指定してるから。

対応として、happy-dom の window.location を beforeAll のときに適当に設定してあげ、かつハンドラ側のURLもそれに合わせることで解決できる。

Coji MizoguchiCoji Mizoguchi

解消した例。
テスト対象コンポーネント(App.vue) は同じなので割愛。

対応済みモックハンドラ

import { rest } from 'msw'

export const handlers = [
  rest.get('http://localhost:3000/hello.txt', (_req, res, ctx) => { // !!! 絶対URLにしておく
    return res(ctx.status(200), ctx.text('Mock!'))
  }),
]

対応済みテストコード

import { render, waitFor, screen } from '@testing-library/vue'
import { server } from '../src/mocks/server'
import App from '../src/App.vue'

describe('describe', async () => {
  beforeAll(() => {
    window.location.href = 'http://localhost:3000' // !!! ブラウザの origin を設定しておく。fetchは相対URLを指定されるとこれを使う。
    server.listen()
  })
  afterEach(() => {
    server.resetHandlers()
  })
  afterAll(() => {
    server.close()
  })

  it('test1', async () => {
    const wrapper = render(App)
    expect(await wrapper.findAllByText('Hello Mock!')).toBeTruthy()
  })
})

vitest実行結果

$ yarn vitest --dom --environment=happy-dom --silent=false --run

 RUN  v0.7.7 /Users/coji/progs/spike/vitest/vitest-example

 √ test/hello.spec.ts (1)

Test Files  1 passed (1)
     Tests  1 passed (1)
      Time  2.76s (in thread 33ms, 8483.38%)

✨  Done in 3.42s.
Coji MizoguchiCoji Mizoguchi

適当に設定するURLは development 環境用の URL に合わせると、web環境でも mock が機能してよい。