5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TSKaigiAdvent Calendar 2024

Day 22

TypeScriptとJestで始める!安全なテスト生活

Last updated at Posted at 2024-12-22

この記事はTSKaigi Advent Calendar 2024 22日目の記事です。

はじめに

こんにちは!TSKaigiクリエイティブチームの添田です!
2025年5月のTSKaigiの開催が待ち遠しくてワクワクが止まりません!

最近、TypeScriptとJestの組み合わせにすっかりハマっています。
皆さんにもその魅力を伝えたいと思います!😊

なぜテストが必要なの?

コードを書いていると、「動作確認はブラウザで十分じゃない?」と思うかもしれません。
でも、自動テストがあると以下のような場面で頼もしい存在になります!

  • ✨ 新機能の追加:既存の機能が壊れていないかすぐ確認できる
  • 🐛 バグ修正後:同じバグが再発していないかチェック可能
  • 👥 チーム開発:他メンバーが修正したコードの動作も保証される

Jestって何がスゴイの?

JestはJavaScript向けの人気テストフレームワークです!

  • 🚀 簡単な書き方:テストの記述がシンプル
  • 👀 見やすい結果表示:エラー箇所が一目でわかる
  • 💪 TypeScript対応:ts-jestを使えばTypeScriptコードもそのままテスト可能

下準備

以下のコマンドでTypeScriptとJestをインストールします。

npm install --save-dev jest typescript ts-jest @types/jest

実践

足し算関数のテスト

まず、シンプルな足し算関数を用意します。

export function sum(a: number, b: number): number {
  return a + b;
}

次に、この関数のテストコードを作成します。

最初にテスト対象となるsum関数をimportします。

import { sum } from "./sum";

it関数はtest関数と同じ動作をしますが、好みに応じて使い分けが可能です。
当記事はit関数を使用しています。

it関数の第1引数は文字列でテストケースの名前を書きます。
英語はもちろん、日本語でも書くことができます。

第2引数はアロー関数を指定し、アロー関数の中にテストを書いていきます!

it("3と2を足すと5になる", () => {});

expect関数を使い、引数にテスト対象の実行結果を渡します。
このtoBeはMatcher関数と呼ばれているもので、期待値を渡すものです。

sum.test.ts
it("3と2を足すと5になる", () => {
 expect(sum(3, 2)).toBe(5);
});

Matcher関数については、他にもあるので、
興味ある方は是非以下を参考にしてみてください。

テストを実行

npm test src/sum.test.ts

成功時:✅ 緑色のマーク
失敗時:❌ 赤色のマーク
見やすい出力で、どこが問題なのかすぐに分かりますね!

成功時

> jest-lesson@1.0.0 test
> jest src/sum.test.ts

 PASS  src/sum.test.ts
  ✓ 3と2を足すと5になる (1 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.102 s, estimated 2 s

失敗時

 FAIL  src/sum.test.ts
  ✕ 3と2を足すと5になる (2 ms)

  ● 3と2を足すと5になる

    expect(received).toBe(expected) // Object.is equality

    Expected: 8
    Received: 5

      2 |
      3 | it("3と2を足すと5になる", () => {
    > 4 |     expect(sum(3, 2)).toBe(8);
        |                       ^
      5 | });
      6 |

      at Object.<anonymous> (src/sum.test.ts:4:23)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        1.116 s, estimated 2 s

このエラーメッセージでは、Expected: 8とReceived: 5が異なるため、テストの期待値が正しいか、または関数が正しい値を返しているかを確認しましょう。

expect(sum(3, 2)).toBe(8);

TypeScriptと組み合わせることで、テストコードそのものも型安全になります。
そのため、テストの記述時に型エラーを防ぐことができ、より信頼性の高いテストが書けます。

ショッピングリストのテストをしてみよう

/**
 * 【演習】
 *  1. `addItem`メソッドが、アイテムをリストに追加できることを確認するテストケース
 *  2. `removeItem`メソッドが、アイテムをリストから削除できることを確認するテストケース
 *  3. `removeItem`メソッドが、存在しないアイテムの削除を試みたときにエラーをスローすることを確認するテストケース
 */

export class ShoppingList {
  public list: string[];

  constructor() {
    this.list = [];
  }

  addItem(item: string): void {
    this.list.push(item);
  }

  removeItem(item: string): void {
    const index = this.list.indexOf(item);
    if (index === -1) {
      throw new Error(`アイテム: ${item} は存在しません`);
    }
    this.list.splice(index, 1);
  }
}

回答例

import { ShoppingList } from "./practice"

describe("演習問題", () => {
    let shoppingList: ShoppingList;

    beforeEach(() => {
        // 各テストケースの実行前に新しいShoppingListインスタンスを作成します。
        shoppingList = new ShoppingList();
    });

    describe("addItem", () => {
        it("1. `addItem`メソッドが、アイテムをリストに追加できることを確認するテストケース", () => {
            shoppingList.addItem("メロン");
            expect(shoppingList.list).toHaveLength(1);  // 配列や文字列のサイズを確認
            expect(shoppingList.list[0]).toBe("メロン");  // 0番目の文字は「メロン」か確認
        })

        it("リストに複数アイテムを追加できる", () => {  // 追加
            shoppingList.addItem("リンゴ");
            shoppingList.addItem("ミカン");
            expect(shoppingList.list).toHaveLength(2);  // 配列や文字列のサイズを確認
            expect(shoppingList.list).toEqual(["リンゴ", "ミカン"]);    // objectを比較するのでtoEqual
        });
    });

    describe("removeItem", () => {
        it("2. `removeItem`メソッドが、アイテムをリストから削除できることを確認するテストケース", () => {
            shoppingList.addItem("メロン");
            shoppingList.addItem("リンゴ");
            shoppingList.removeItem("リンゴ");
            expect(shoppingList.list).not.toContain("リンゴ");  // アイテムが配列内にないことを確認
            expect(shoppingList.list).toHaveLength(1);  // 配列や文字列のサイズを確認
            expect(shoppingList.list[0]).toBe("メロン");  // 0番目の文字は「メロン」か確認
        })

        it("3. `removeItem`メソッドが、存在しないアイテムの削除を試みたときにエラーをスローすることを確認するテストケース", () => {
            // エラーがスローされることを確認
            expect(() => {
                shoppingList.removeItem("リンゴ");
            }).toThrow("アイテム: リンゴ は存在しません");
        })
    });
})

テストを書くときのコツ📝

  1. テストケースは具体的に書く
    • 「〜の場合、〜になること」という形式で書くと分かりやすい
    • 一つのテストでは一つの機能だけを確認する
  2. beforeEachを活用
    • テストの前準備はbeforeEachでまとめると便利
    • テストケース間の独立性が保てる
  3. エラーケースも忘れずに
    • 正常系だけでなく、異常系のテストも大切
    • ユーザーの予期せぬ操作にも対応できる

さらなる一歩へ!

Jestには他にもたくさんの機能があります

  1. 🎭 モック(Mock)を使ったテスト
  2. 📸 スナップショットテスト
  3. 📊 テストカバレッジの計測

まとめ

今回は、TypeScriptとJestを使った基本的なテストの書き方を学びました!
はじめは少し難しく感じるかもしれませんが、テストを書くことで

  • 💗コードの品質が上がる
  • 💗バグの早期発見ができる
  • 💗安心してリファクタリングできる

といった素晴らしいメリットがあります。
ぜひ、みなさんも実践してみてください!

📅イベント情報📅

TSKaigi2025が2025年5月23日/24日に開催されます!🎪
日本最大級のTypeScriptカンファレンスで、前回は2000人以上の方が参加されました!👥
TypeScriptに興味のある方は、ぜひチェックしてください!✨

TSKaigi 2025 ティザーサイト:https://2025.tskaigi.org/
公式サイト:https://tskaigi.org/
X:https://x.com/tskaigi

参考にさせていただいた資料

Jest公式ドキュメント:https://jestjs.io/ja/
TypeScript公式ドキュメント:https://www.typescriptlang.org/
Udemy教材:https://www.udemy.com/course/ts-webapp-test/?couponCode=ST21MT121624

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?