2
1

単体テストマニュアル3 - Vitest 紹介

Last updated at Posted at 2024-07-23

Vitest とは

Vitest は、特にパフォーマンスの最適化や最新の JavaScript 機能への対応において、Jest に比べて、よりモダンなテストフレームワークです。

なぜ Vitest を選択

  1. Vite ベースの設計

    Vite 上に構築されており、Vite の強力なビルドと最適化機能を活用しています。

    Vite の高速なホットリロード技術を利用し、テストの起動と実行が非常に速いです。

    この特徴により、特に大規模なプロジェクトにおいて、テストの実行速度が大幅に向上します。

    開発者にとって使いやすく、効率的なテスト体験を提供することで、プロジェクトの品質向上に貢献しています。

  2. ES6 との互換性

    ES モジュールを含む、最新の JavaScript 機能を完全にサポートしています。

  3. 設定不要のゼロコンフィグレーション

    Vitest は追加設定なしで、そのまま使用できます。

    Jest は設定やサードパーティのライブラリ(@types/jest, ts-jest など)の追加が必要です。

    また、Vitest は Vite の設定を利用できるので、Vite を使ったプロジェクトにはとても便利です。

  4. Jest から Vitest への移行の容易性

    Jest の API 名と比較して、グローバル API や vi の API レベル以外に、大きな変更がないため、Vitest への移行は容易です。

  5. コミュニティの活動度

    Vitest のコミュニティ活動は Jest に比べて活発です。

Vitest コマンド

現在のディレクトリで vitest コマンドを起動します。

開発環境では自動的にウォッチ(watch)モードに入り、CI 環境では自動的に実行(run)モードになります。

vitest を実行する際に、パラメーターを追加することでテストファイルのフィルタリングができます。

# api.spec.ts のみをテストします
vitest api

Vitest の基本的な API の紹介

testit

testit のエイリアスです。

import { test, it } from "vitest";

describe("", () => {
  it("should do something", () => {});
  it("should do something", () => {});
});

test("should do something", () => {});

各テストケースでは一つの概念のみをテストして、なるべく assertion の数を少なくするようにしてください。

単体テストの安定性と保守性を確保するために、テストケース間では相互に呼び出しを行うべきではなく、実行の順序に依存してもいけません。

悪い例:testCase2testCase1 の実行結果に依存し、その結果を testCase2 の入力として使用する場合。

テストの構造を整理し、より読みやすくするために、

  • describe を使用してテストをグループ化する場合は it でテストケースを記述し、
  • describe を使用しない場合は test を用いてテストケースを作成します。

describe テストスイート

describe を使用すると、現在のコンテキストで新しいスイートを定義できます。

これは関連するテストやベンチマーク、その他のネストされたスイートのセットです。

describe スイートをよく使用して、テストやベンチマークを整理して、レポートをより明確にすることがとても大事です。

describe ブロックをネストすることもできます。

describe("useUsers", () => {
  beforeEach(() => {
    console.log("beforeEach");
  });

  describe("successful cases", () => {
    beforeEach(() => {
      console.log("beforeEach");
    });

    it("handles successful use user when the users have email", async () => {});

    it("handles successful use user when the users haven't email", async () => {});
  });

  describe("failed cases", () => {
    beforeEach(() => {
      console.log("beforeEach");
    });

    it("handles use user failures", async () => {});

    it("handles use user failures where the listUsersInGroup function returns without a Users attribute.", async () => {});

    it("handles use user failures when the allGroups is [] and the fltGroup is ''", async () => {});
  });
});

expect

expect はアサーションを作成するために使用されます。

toBetoEqual

  • toBe はプリミティブや同じ参照を共有するオブジェクトに使用されます。

    toBeプリミティブが等しいか、または オブジェクトが同じ参照を共有している ことをアサートするために使用できます。

    JavaScript では、プリミティブ(原始値、原始データ型)はオブジェクトではなく、メソッドやプロパティを持たないデータです。

    7 つのプリミティブデータ型があります:nullundefinedbooleannumberstringsymbolBigInt

    toBe=== と同じです。

  • toEqual は同じ参照を共有しない値/オブジェクト(Error オブジェクトを除く)に使用されます。

    オブジェクトが同じではなくても、構造が同一であるかどうかを確認したい場合は toEqual を使用できます。

    toEqual実際の値が受け取った値と等しいか、または**オブジェクトであれば同じ構造を持つ(再帰的に比較する)**ことをアサートします。

    Error オブジェクトに対しては深い等価性は行われません。

    何かがスローされたかどうかをテストするには、toThrowError アサーションを使用してください。

import { test, expect } from "vitest";

test("toBe", () => {
  expect(1).toBe(1);
});

test("toEqual", () => {
  const user = {
    name: "nansen",
  };
  expect(user).toEqual({
    name: "nansen",
  });
});

toBeTruthytoBeFalsy

toBeTruthy は値が Boolean に変換されたときに真であることをアサートします。

toBeFalsy は値が Boolean に変換されたときに偽であることをアサートします。

JavaScript では、false0-00n""(空文字列)、nullundefinedNaN を除くすべての値が truthy と評価されます。

import { expect, test } from "vitest";

test("toBeTruthy", () => {
  expect(1).toBeTruthy();
});

toContain

toContain は、実際の値が配列内にあるかどうかをアサートします。

また、ある文字列が別の文字列の一部文字列であるかどうかもチェックできます。

import { expect, it } from "vitest";

const item1 = { name: "nansen" };
const item2 = { name: "erica" };
const list = [item1, item2];

it("toContain", () => {
  expect(list).toContain(item1);
});

toThrowErrortoThrow

toThrowtoThrowError のエイリアスです。

toThrowError は、関数が呼び出された際にエラーを投げるかどうかをアサートします。

特定のエラーが投げられるかをテストするために、オプショナルな引数を提供することができます:

  • 正規表現:エラーメッセージがパターンに一致する。
  • 文字列:エラーメッセージにその部分文字列が含まれている。
import { expect, test } from "vitest";

function sayHi(name) {
  if (typeof name !== "string") {
    throw new Error("wrong name");
  }
  return `Hi, ${name}!`;
}

test("toThrow", () => {
  expect(() => sayHi(111)).toThrow("wrong");
});

beforeEachbeforeAllafterEachafterAll

  • beforeEach は、現在のコンテキストで実行される各テストの前に一度呼び出されるコールバックを登録します。

    test() が呼び出される回数と同じ回数、beforeEach() が呼び出されます。

  • beforeAll は、現在のテストスイートのすべてのテストケースが実行される前に一度だけ呼び出されるコールバック関数を設定するためのものです。

    関数がプロミスを返す場合、Vitest はそのプロミスが解決されるまで、テストの実行を待機します。

  • afterEach は、各テストが完了した後に実行されるコールバックを登録します。

  • afterAll は、現在のコンテキストでのすべてのテストが完了した後に一度だけ呼び出されるコールバックを登録します。

    関数がプロミスを返す場合、Vitest はプロミスが解決するまで待ちます。

import {
  describe,
  it,
  beforeEach,
  afterEach,
  beforeAll,
  afterAll,
} from "vitest";

describe("", () => {
  beforeEach(async () => {
    // 各テスト実行前にいくつかのテストデータを追加
    await addUser({ name: "John" });
  });

  afterEach(async () => {
    // 各テストが完了した後にテストデータをクリアします。
    await clearTestingData();
  });

  beforeAll(async () => {
    // すべてのテストが実行される前に一度呼び出されます。
    await mockSomething();
  });

  afterAll(() => {
    // このメソッドはすべてのテストが実行された後に呼び出されます。
    resetGlobalData();
  });

  it("", () => {});
  it("", () => {});
  it("", () => {});
});

以下のように、beforeEachbeforeAllクリーンアップ関数をオプションとして受け入れることができます。これは afterEachafterAll と同じです。

import { beforeEach, beforeAll } from "vitest";

beforeEach(async () => {
  // 各テストが実行される前に一度呼び出されます。
  await prepareSomething();

  // クリーンアップ関数、
  // 各テスト実行後に一度呼び出されます。
  return async () => {
    await resetSomething();
  };
});

beforeAll(async () => {
  // すべてのテストが実行される前に一度呼び出されます。
  await startMocking();

  // クリーンアップ関数、
  // すべてのテスト実行後に一度呼び出されます。
  return async () => {
    await stopMocking();
  };
});

以下のコードでは、各部分の実行順序を考えてください。解答は記事の最後にあります。

import {
  beforeAll,
  beforeEach,
  afterAll,
  afterEach,
  describe,
  it,
} from "vitest";

beforeAll(() => {
  console.log("beforeAll");
});

beforeEach(() => {
  console.log("beforeEach");
});

it("", () => {
  console.log("it");
});

describe("nested", () => {
  beforeEach(() => {
    console.log("nested beforeEach");
  });
  it("nested it", () => {
    console.log("nested it");
  });
  afterEach(() => {
    console.log("nested afterEach");
  });
});

afterEach(() => {
  console.log("afterEach");
});

afterAll(() => {
  console.log("afterAll");
});

onlyskiptodo フィルター

onlyskiptodo を使用して、実行するテストファイルをフィルタリングできます。

  • only は指定された部分のコードのみを実行するように設定します。

    test.only("", () => {});
    bench.only("", () => {});
    describe.only("", () => {});
    
  • skip は指定された部分のコードを実行しないように設定します。

    test.skip("", () => {});
    bench.skip("", () => {});
    describe.skip("", () => {});
    
  • todo は指定された部分のコードを後で実装するように保留します。

    テストレポートにはエントリが表示され、実行する必要があるテストの数を知ることができます。

    test.todo("", () => {});
    bench.todo("", () => {});
    describe.todo("", () => {});
    

解答

import {
  beforeAll,
  beforeEach,
  afterAll,
  afterEach,
  describe,
  it,
} from "vitest";

// 1
beforeAll(() => {
  console.log("beforeAll");
});

// 2 5
beforeEach(() => {
  console.log("beforeEach");
});

// 3
it("", () => {
  console.log("it");
});

describe("nested", () => {
  // 6
  beforeEach(() => {
    console.log("nested beforeEach");
  });
  // 7
  it("nested it", () => {
    console.log("nested it");
  });
  // 8
  afterEach(() => {
    console.log("nested afterEach");
  });
});

// 4 9
afterEach(() => {
  console.log("afterEach");
});

// 10
afterAll(() => {
  console.log("afterAll");
});
2
1
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
2
1