Open8
そろそろテストを学習する
参考
テストとは
ソフトウェアの品質を担保するためのもの
フロントエンドのテストは開発者の意図通りに機能が使用できるか
データベースなどは意識せず、あくまで画面上の動作
なぜ必要なのか
- 開発中の早い段階でエラーを発見できる
- 本番リリース前に問題を修正できる(デグレがないことが担保できる)
- 国際化対応やアクセシビリティなどの特定の観点からチェックできる
特に有名なテストの種類
テストトロフィー
- 上のレイヤーに行くほどコストが高く速度が遅いが信頼性は高くなる
ユニットテスト(単体テスト)
- 関数やメソッドのテスト
- Jestが最も使われている(最近はVitestも普及)
- 本番のAPIではなくモックデータが使用される
インテグレーションテスト(結合テスト)
- ユニット間のインタラクションのテスト
- TestingLibraryがよく使われている
- 機能ごとの単体テストに対して、複数の機能が連動するかをテストする
E2Eテスト(エンドツーエンドテスト)
- ブラウザを動作させて行うインタラクションテスト
- Cypressがよく使われている(最近はPlaywrightが普及)
- コードや実装とは一切関係なくユーザーからアプリケーションがどう見えるか・どのように動かすかをテストする
- モックではなく実際のAPIを使う
スタティックテスト
- コードのフォーマットやタイポ、型のテスト
- ESLintやPrettierなど
一旦テストを自作してみる
(関数名をjestで使うもに合わせた)
// テストする関数
function sum(a, b) {
return a + b
}
// テストケース
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3)
})
test('adds 3 + 4 to equal 7', () => {
expect(sum(3, 4)).toBe(8)
})
// テスト処理の実装
async function test(title, callback) {
try {
await callback()
console.log(`✅ ${title}`)
} catch (error) {
console.error(`❌ ${title}`)
console.error(error)
}
}
function expect(result) {
return {
toBe(expected) {
if (result !== expected) {
throw new Error(`${result} is not equal to ${expected}`)
}
},
}
}
実際にjestを使ってみる
今回はts-jest
を使用
インストール
yarn add --dev ts-jest
ファイル作成
sum.ts
function sum(a, b) {
return a + b
}
module.exports = sum
sum.test.ts
const sum = require('./sum')
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3)
})
実行
yarn test
コンポーネントテスト(ユーザーイベント(クリック))
簡易コンポーネント作成
ボタンをクリックするたびに表示がON,OFF切り替わるコンポーネントを作成
src/components/SinmpleButton.tsx
import { useState } from 'react'
export const SimpleButton: () => JSX.Element = () => {
const [state, setState] = useState(false)
const handleClick = () => {
setState((prevState) => !prevState)
}
return <button onClick={handleClick}>{state ? 'ON' : 'OFF'}</button>
}
テストコード
src/components/SimpleButton.test.tsx
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import '@testing-library/jest-dom'
import { SimpleButton } from './SimpleButton'
test('ボタンをクリックするとON/OFFの表示が切り替わる', () => {
render(<SimpleButton />)
const simpleButton = screen.getByRole('button')
expect(simpleButton).toHaveTextContent('OFF')
// ボタンをクリックして、テキストが変更されることを確認する
userEvent.click(simpleButton)
expect(simpleButton).toHaveTextContent('ON')
})
はまったところ
- コンポーネントテストの際は拡張子を.tsxにしないといけない
- テストの実行環境がnodeになっていたのでjsdomでないといけない
コンポーネントテスト(ユーザーイベント(入力フォーム))
入力フォームコンポーネントを作成
src/copmponents/SearchForm.tsx
import { ChangeEvent, FC, useState } from 'react'
export const SearchForm: FC = () => {
const [value, setValue] = useState<string>('')
const onchange = (e: ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value)
}
const onclick = () => {
setValue('')
}
return (
<>
<input type="text" onChange={onchange} value={value} />
<button onClick={onclick}>検索</button>
</>
)
}
入力フォームコンポーネントテスト作成
src/components/SearchFrom.test.tsx
import { render, screen } from '@testing-library/react'
import '@testing-library/jest-dom/extend-expect'
import userEvent from '@testing-library/user-event'
import { SearchForm } from './SearchForm'
describe('input form onchange and button onclick event', () => {
it('input test', () => {
render(<SearchForm />)
const inputValue = screen.getByRole('textbox') as HTMLInputElement
const searchBtn = screen.getByRole('button')
// インプットの中にtestと打った時にtestと入力されているか
userEvent.type(inputValue, 'test')
expect(inputValue.value).toBe('test')
// 検索ボタンを押した際にインプットの中身が空になるかテスト
userEvent.click(searchBtn)
expect(inputValue.value).toBe('')
})
})
解説
-
.getByRole
で入力フォームの要素のボタンの要素をそれぞれ取得 - 型推論が
HTMLElement
なのでHTMLInputElement
をas(型アサーション)で付与 -
userEvent.type
でユーザーのタイピングを実現させる -
userEvent.click
でユーザのクリックを実現させる -
expect(inputValue.value)
でその時点での要素の中身を確認