Open4

Vitestと格闘している様子を実況

webdevwebdev

環境

library version
react 18.2.0
react-redux 8.1.2
react-router-dom 6.16.0
typescript 5.0.2
vite 4.4.5
vitest 0.34.5
webdevwebdev

設定

ルートディレクトリへvitest.config.tsを設置し設定を記述する

/// <reference types="vitest" />

import { defineConfig } from 'vite'

export default defineConfig({
  test: {
    globals: true, // viなどのAPIをimportなしで使用できる
    environment: 'happy-dom',
    alias: {
      components: '/src/components',
      // vite.config.tsにaliasを設定している場合はvitest.config.tsにも書く
    },
    setupFiles: './vitest-setup', // テストの前処理として実行したいコードを設定できる
  },
})

https://vitest.dev/config/

webdevwebdev

仮想のURLを設定したテスト

<Router>createMemoryHistoryhistoryを設定すると噛み合った

import { Provider } from 'react-redux'

import { Routes, Router, Route } from 'react-router-dom'

import { render, screen } from '@testing-library/react'
import { createMemoryHistory } from 'history'

import { store } from 'store' // 環境にあわせてstoreへのパスを設定
import { App } from './App' // テスト対象のコンポーネント

const history = createMemoryHistory()

// 共通処理: render
const initial = (path: string) => {
  history.push(path)
  return render(
    <Provider store={store}>
      <Router location={history.location} navigator={history}>
        <Routes>
            <Route path={このコンポーネントを表示したいpath} element={<App />} />
        </Routes>
      </Router>
    </Provider>
  )
}

describe('メインナビのテスト', () => {

  test('/hogeにアクセスすると、fuga', () => {
    const path = '/hoge'
    initial(path)

    // URLが"/hoge"のときAppがどうなっていてほしいか
  })

})

webdevwebdev

useLocationのモック 効いていなかった

1件だけならモックアップ成功させられるも、リセットに失敗している。

1件目のtestメニューを使用するlocationを含んでいる場合は表示は成功するのに
2件目メニューを使用するlocationを含んでいない場合は非表示は成功しない。
1件目と2件目を入れ替えると最初のtestが成功する。

import { renderHook } from '@testing-library/react'

import { useMainNavigation } from './useMainNavigation'

describe('メインナビのテスト', () => {
  afterEach(() => {
    vi.doUnmock('react-router-dom')
  })

  test('メニューを使用するlocationを含んでいる場合は表示', () => {
    vi.mock('react-router-dom', async () => {
      const actualRedux = await vi.importActual('react-router-dom')
      return {
        ...(actualRedux as any),
        useLocation: vi.fn(() => {
          return { pathname: '/information' }
        }),
      }
    })
    const { result } = renderHook(() => useMainNavigation())
    expect(result.current.isVisible).toBe(true)
  })

  test('メニューを使用するlocationを含んでいない場合は非表示', () => {
    vi.mock('react-router-dom', async () => {
      const actualRedux = await vi.importActual('react-router-dom')
      return {
        ...(actualRedux as any),
        useLocation: vi.fn(() => {
          return { pathname: '/login' }
        }),
      }
    })
    const { result } = renderHook(() => useMainNavigation())
    expect(result.current.isVisible).toBe(false)
  })
})

共通関数化に失敗

vi.mock('react-router-dom'〜をこのような共通関数にすると
valueundefinedというエラーでテストが実行できなくなる

import { renderHook } from '@testing-library/react'

import { useMainNavigation } from './useMainNavigation'

const setupLocationMock = (value: string) => {
  console.log('Setting up mock with pathname:', pathname);
  vi.mock('react-router-dom', async () => {
    const actualRedux = await vi.importActual('react-router-dom');
    console.log('Mocking useLocation with pathname:', pathname);
    return {
      ...(actualRedux as any),
      useLocation: vi.fn(() => {
        console.log('useLocation mock called');
        return { pathname: value };
      }),
    };
  });
}

describe('メインナビのテスト', () => {
  afterEach(() => {
    vi.doUnmock('react-router-dom')
  })

  test('メニューを使用するlocationを含んでいる場合は表示', () => {
    setupLocationMock('/information')
    const { result } = renderHook(() => useMainNavigation())
    expect(result.current.isVisible).toBe(true)
  })
})