
変数とデータ型(let, const, プリミティブ型)
はじめに JavaScriptを学ぶ上で、変数とデータ型の理解は基礎中の基礎です。この知識なしでは効果的なプログラミングはできません。本記事では、現代のJava […]
ソフトウェア開発において、テストは品質保証の重要な手段です。この章では、JavaScriptのテストについて、初心者の方にもわかりやすく基本から実践まで詳しく解説します。テストの重要性、主要なテストツール、実際のテストの書き方、ベストプラクティスまで、実践的な知識を身につけましょう。
テストとは、コードが期待通りに動作することを確認するためのプロセスです。自動化されたテストを書くことで、以下のようなメリットが得られます:
JavaScriptには豊富なテストツールがあります。主要なツールを紹介します:
npm install --save-dev jest
package.jsonに以下を追加:
{
"scripts": {
"test": "jest"
}
}
テスト対象の関数(sum.js):
function sum(a, b) {
return a + b;
}
module.exports = sum;
テストファイル(sum.test.js):
const sum = require('./sum');
test('1 + 2 は 3', () => {
expect(sum(1, 2)).toBe(3);
});
test('不正な入力でエラーを投げる', () => {
expect(() => sum('1', '2')).toThrow('引数は数値でなければなりません');
});
テスト実行:
npm test
describe('数学関数のテスト', () => {
it('2つの数値を加算する', () => {
expect(sum(1, 2)).toBe(3);
});
it('負の数も加算できる', () => {
expect(sum(-1, -2)).toBe(-3);
});
});
// 等値
expect(result).toBe(3); // プリミティブ値
expect(result).toEqual({ a: 1 }); // オブジェクトの深い比較
// 真偽値
expect(result).toBeTruthy();
expect(result).toBeFalsy();
// エラー
expect(() => func()).toThrow('エラーメッセージ');
// 配列
expect(array).toContain('item');
// 非同期
await expect(asyncFunc()).resolves.toBe('result');
beforeAll(() => {
// 全テストの前に行う設定
});
afterEach(() => {
// 各テスト後に実行するクリーンアップ
});
describe('スコープ付きフック', () => {
beforeEach(() => {
// このdescribeブロック内の各テスト前に実行
});
});
test('非同期関数のテスト', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
test('async/awaitで非同期テスト', async () => {
const data = await fetchData();
expect(data).toBe('peanut butter');
});
const mockFunc = jest.fn();
mockFunc('arg1', 'arg2');
expect(mockFunc).toHaveBeenCalled();
expect(mockFunc).toHaveBeenCalledWith('arg1', 'arg2');
jest.mock('../api');
test('APIモジュールのモック', async () => {
const api = require('../api');
api.getData.mockResolvedValue('mock data');
const data = await fetchData();
expect(data).toBe('mock data');
});
import { render, screen, fireEvent } from '@testing-library/react';
import Button from './Button';
test('ボタンクリックでハンドラが呼ばれる', () => {
const handleClick = jest.fn();
render();
fireEvent.click(screen.getByText('Click me'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
test('スナップショットテスト', () => {
const { container } = render();
expect(container.firstChild).toMatchSnapshot();
});
describe('ログインフローのテスト', () => {
it('成功的なログイン', () => {
cy.visit('/login');
cy.get('#username').type('testuser');
cy.get('#password').type('password123');
cy.get('form').submit();
cy.url().should('include', '/dashboard');
cy.contains('Welcome, testuser');
});
});
jest --coverage
でカバレッジレポートを生成可能TDDは「テストファースト」の開発手法で、以下のサイクルで進めます:
テストが失敗した時のデバッグ方法:
test.only
)console.log
で中間値を確認--inspect-brk
オプション)JavaScriptテストの重要なポイント:
テストを書く習慣を身につけることで、より堅牢でメンテナンス性の高いコードを書けるようになります。
以下の関数をテストするコードをJestで書いてください。正常系と異常系の両方を含めてください。
function divide(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new Error('引数は数値でなければなりません');
}
if (b === 0) {
throw new Error('0で除算できません');
}
return a / b;
}
次のテストに関する記述について、正しいものには○、間違っているものには×をつけてください。
以下のReactコンポーネントをTesting Libraryでテストしてください。ボタンクリック時の動作と初期表示を検証してください。
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p data-testid="count">Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
describe('divide関数のテスト', () => {
test('正常な除算', () => {
expect(divide(10, 2)).toBe(5);
expect(divide(9, 4)).toBe(2.25);
});
test('0除算でエラー', () => {
expect(() => divide(10, 0)).toThrow('0で除算できません');
});
test('非数値入力でエラー', () => {
expect(() => divide('10', '2')).toThrow('引数は数値でなければなりません');
expect(() => divide(null, undefined)).toThrow('引数は数値でなければなりません');
});
});
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';
describe('Counterコンポーネント', () => {
test('初期表示は0', () => {
render( );
expect(screen.getByTestId('count')).toHaveTextContent('Count: 0');
});
test('Incrementボタンでカウントアップ', () => {
render( );
fireEvent.click(screen.getByText('Increment'));
expect(screen.getByTestId('count')).toHaveTextContent('Count: 1');
fireEvent.click(screen.getByText('Increment'));
expect(screen.getByTestId('count')).toHaveTextContent('Count: 2');
});
test('Resetボタンでカウントリセット', () => {
render( );
fireEvent.click(screen.getByText('Increment'));
fireEvent.click(screen.getByText('Increment'));
fireEvent.click(screen.getByText('Reset'));
expect(screen.getByTestId('count')).toHaveTextContent('Count: 0');
});
});