・Next.js: 13.5.2
・Vitest: 0.34.4
・react-testing-library: 14.0.0
・happy-dom: 12.1.5
・coverage-v8: 0.34.4
1. 以下のコマンドを入力し、Vitestの実行に必要なライブラリをインストールします。
$ npm install --save-dev vitest @testing-library/react happy-dom @vitejs/plugin-react
2. プロジェクトのルート配下に「vitest.config.ts」を新規作成し、以下の内容で保存します。
// vitest.config.ts
import react from '@vitejs/plugin-react'
import path from 'path'
import { defineConfig } from 'vitest/config'
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'happy-dom',
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'~': path.resolve(__dirname, './src'),
},
},
})
3. 「package.json」のscriptsセクションに以下を追加します。
{
"scripts": {
"test": "vitest",
}
}
4. 以下のコマンドを入力することで、Vitestを実行して単体テストを実施することができます。
$ npm run test
> vitest
✓ src/unit-test/test.spec.tsx (1)
✓ some test...
Test Files 1 passed (1)
Tests 1 passed (1)
Start at 18:36:43
Duration 154ms
※現時点ではカバレッジが表示されないため、次項ではVitest実行時のテスト結果にカバレッジを表示する手順を記載しています。
1. 以下のコマンドを入力し、カバレッジの取得に必要なライブラリをインストールします。
$ npm install --save-dev @vitest/coverage-v8
2. 「vitest.config.ts」のtestセクションに以下を追加します。
// vitest.config.ts
import react from '@vitejs/plugin-react'
import path from 'path'
import { defineConfig } from 'vitest/config'
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'happy-dom',
// ここから追加
coverage: {
provider: 'v8',
include: ['src/**/*.{tsx,js,ts}'],
all: true,
reporter: ['html', 'clover', 'text']
},
root: '.',
reporters: ['verbose'],
// ここまでを追加
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'~': path.resolve(__dirname, './src'),
},
},
})
3. 「package.json」のscriptsセクションに追加したvitestコマンドに「--coverage」を追加します。
{
"scripts": {
"test": "vitest --coverage",
}
}
4. 再度Vitestを実行して単体テストを実施し、カバレッジが表示されていることを確認します。
$ npm run test
> vitest
✓ src/unit-test/test.spec.tsx (1)
✓ some test...
Test Files 1 passed (1)
Tests 1 passed (1)
Start at 18:36:43
Duration 154ms
% Coverage report from v8
-------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------|---------|----------|---------|---------|-------------------
All files | 9.39 | 50 | 50 | 9.39 |
app | 0 | 0 | 0 | 0 |
layout.tsx | 0 | 0 | 0 | 0 | 1-22
page.tsx | 0 | 0 | 0 | 0 | 1-113
app/test | 100 | 100 | 100 | 100 |
page.tsx | 100 | 100 | 100 | 100 |
-------------|---------|----------|---------|---------|-------------------
'use client'
import { useState } from 'react'
export default function Test() {
const [count, setCount] = useState(0)
const handleClick = () => {
setCount(count + 1)
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={handleClick}>Click</button>
</div>
)
}
import { render, fireEvent } from '@testing-library/react'
import { expect, test, describe } from 'vitest'
import Test from './page'
describe('test/page.tsxのテスト', () => {
describe('画面の初期表示確認', () => {
test('クリック数が初期表示として「0」が表示されていること', () => {
// Arrange
const { getByText } = render(<Test />)
// Assert
expect(getByText('You clicked 0 times')).toBeDefined()
})
})
test('ボタンをクリックした場合、クリック数が画面に表示されていること', () => {
// Arrange
const { getByText } = render(<Test />)
// Act
fireEvent.click(getByText('Click'))
// Assert
expect(getByText('You clicked 1 times')).toBeDefined()
})
})
$ npm run test
✓ src/app/test/page.test.tsx (2)
✓ test/page.tsxのテスト (2)
✓ 画面の初期表示確認 (1)
✓ クリック数が初期表示として「0」が表示されていること
✓ ボタンをクリックした場合、クリック数が画面に表示されていること
Test Files 1 passed (1)
Tests 2 passed (2)
Start at 18:59:31
Duration 161ms
% Coverage report from v8
-------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------|---------|----------|---------|---------|-------------------
All files | 12.9 | 66.66 | 33.33 | 12.9 |
app | 0 | 0 | 0 | 0 |
layout.tsx | 0 | 0 | 0 | 0 | 1-22
page.tsx | 0 | 0 | 0 | 0 | 1-113
app/test | 100 | 100 | 100 | 100 |
page.tsx | 100 | 100 | 100 | 100 |
-------------|---------|----------|---------|---------|-------------------
※JSONPlaceholder:RESTで実装されたAPIサーバー。ダミーデータを返す。
※notFound()関数:Next.jsの機能の1つ。notFound関数をコールすると、not-found.tsxを描画することができます。詳細はこちら。
import { notFound } from 'next/navigation'
export default async function Test() {
const posts = await fetch(`https://jsonplaceholder.typicode.com/posts`).then((res) => res.json())
if (!posts || posts.length == 0) {
notFound()
}
return (
<div>
<h2>Post List</h2>
{posts.map((post) => (
<div key={post.id}>
<h3>{post.title}</h3>
<p title={post.body}>{post.body}</p>
</div>
))}
</div>
)
}
import { render } from '@testing-library/react'
import { expect, test, describe, vi, afterEach } from 'vitest'
import Test from './page'
const notFoundMock = vi.hoisted(() => vi.fn())
const responseData = [
{
id: 1,
title: 'test title 1',
body: 'test body 1',
},
{
id: 2,
title: 'test title 2',
body: 'test body 2',
},
]
describe('test/page.tsxのテスト', () => {
const response = {} as Response
vi.mock('next/navigation', () => ({
notFound: notFoundMock,
}))
afterEach(() => {
vi.restoreAllMocks()
})
test('投稿の一覧が画面に表示されていること', async () => {
// Arrange
response.json = vi.fn().mockResolvedValue(responseData)
vi.spyOn(global, 'fetch').mockResolvedValue(response)
const { getByText } = render(await Test())
// Assert
expect(getByText('test title 1')).toBeDefined()
expect(getByText('test body 1')).toBeDefined()
expect(getByText('test title 2')).toBeDefined()
expect(getByText('test body 2')).toBeDefined()
expect(notFoundMock).not.toBeCalled()
})
test('投稿が取得できなかった場合、notFound関数がコールされること', async () => {
// Arrange
response.json = vi.fn().mockResolvedValue([])
vi.spyOn(global, 'fetch').mockResolvedValue(response)
render(await Test())
// Assert
expect(notFoundMock).toHaveBeenCalledOnce()
})
})
$ npm run test
✓ src/app/test/page.test.tsx (2)
✓ test/page.tsxのテスト (2)
✓ 投稿の一覧が画面に表示されていること
✓ 投稿が取得できなかった場合、notFound関数がコールされること
Test Files 1 passed (1)
Tests 2 passed (2)
Start at 19:14:17
Duration 846ms (transform 34ms, setup 0ms, collect 146ms, tests 13ms, environment 127ms, prepare 77ms)
% Coverage report from v8
-------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------|---------|----------|---------|---------|-------------------
All files | 12.9 | 66.66 | 33.33 | 12.9 |
app | 0 | 0 | 0 | 0 |
layout.tsx | 0 | 0 | 0 | 0 | 1-22
page.tsx | 0 | 0 | 0 | 0 | 1-113
app/test | 100 | 100 | 100 | 100 |
page.tsx | 100 | 100 | 100 | 100 |
-------------|---------|----------|---------|---------|-------------------
以上で全ての手順は完了になります