Advanced (更新: 2026/7/22)

Vitestのモックとカバレッジで、Claude Codeのテストを“落ちない”状態にする

Claude Codeが書いたVitestのテストがCIで落ちる。原因はモックの巻き上げとタイマーの後始末。vi.mockの使い方、カバレッジ計測、落ちたテストを直させる手順を実例で。

Vitestのモックとカバレッジで、Claude Codeのテストを“落ちない”状態にする

注文フォームの修正をClaude Codeに任せ、追加されたVitestもローカルではすべて成功した。それなのに、プルリクエストのCIでは同じテストが落ちる。この状態では、テストが増えるほどレビューに時間がかかります。

よくある原因は、モックの戻し忘れ、偽タイマーの後始末、非同期処理の待ち忘れ、Nodeバージョンの差です。単独実行では成功しても、同じファイルの別テストと続けて実行すると失敗する場合があります。

この記事では、原因を推測だけで直さないために、API境界、時刻、DOMの3ケースを実際に動くコードへ分けます。最後に、Claude Codeへ渡す修正プロンプトと、人が承認すべき範囲も示します。

対象は単体テストと小さな結合テストです。vi.fn()vi.mock() の使い分け、カバレッジの読み方、落ちたテストをClaude Codeに直させる手順を扱います。実ブラウザのE2EはPlaywright E2Eテスト実践ガイドへ分けます。

この記事の要点

  • Claude Codeのテストがフレーキー(たまに落ちる)になる主犯は、モックとタイマーの後始末忘れvitest.config.tsrestoreMocks: trueafterEach で大半は消える。
  • vi.mock() は書いた場所に関係なくファイル先頭に巻き上げられる。モジュールごと差し替えるならこれ、依存を1個だけ偽物にするなら vi.fn() を引数で渡すほうが事故が少ない。
  • カバレッジは数字を上げるゲームではなく、テストし忘れた分岐を探すレーダーcoverage.include を書かないと未テストの新規ファイルが見えない。
  • Claude Codeには「成功条件」だけでなく「失敗条件」と「実行コマンド」をセットで渡す。これだけで実務で踏む穴が埋まる。
  • CIのスクリプトは vitest run と明示する。VitestはCIや非対話環境では単発実行へ切り替わるが、コマンドの意図が明確になり、ローカルの監視モードと混同しにくい。

2026年7月22日に、Vitest CLIwatch設定Vitest 4移行ガイドを確認しました。Vitest 4はVite 6以上、Node 20以上が前提です。以下のコードはこの条件で検証します。

flowchart TD
  A["変更対象を決める"] --> B["外部依存を境界で分ける"]
  B --> C["API: vi.fn で置き換える"]
  B --> D["時刻: fake timers で固定する"]
  B --> E["DOM: jsdom で構造を確認する"]
  C --> F["vitest run"]
  D --> F
  E --> F
  F --> G["人が差分と本番影響を承認する"]

なぜ「緑なのに落ちる」が起きるのか

テストが状態を持つから、です。

イメージとしては、共用キッチンで料理するのに似ています。前の人が使ったフライパンを洗わずに次の人が使うと、味が混ざる。テストも同じで、あるテストが偽物に差し替えた関数や、止めた時計を、そのまま放置すると、次のテストがその汚れた状態を引き継いでしまう。

同じテストファイルでは、前のテストが残した時計やDOMを後続テストが引き継ぐことがあります。Vitestは通常テストファイルを分離しますが、isolate: false を選んだ構成や、プロセス外の共有資源を使うテストではファイル間の影響も起こり得ます。まずは同じファイルの後始末から確認します。

Claude Codeは1個のテストを書くのはうまい。でも「他のテストと一緒に走ったときの汚れ」までは、指示しないと面倒を見てくれません。だから設定とプロンプトで、後始末を強制する必要があります。

まず設定で“掃除”を自動化する

最初に依存を入れ、vitest.config.ts に掃除とカバレッジのルールを書きます。

npm install -D vitest @vitest/coverage-v8 jsdom typescript
{
  "scripts": {
    "test": "vitest",
    "test:run": "vitest run",
    "coverage": "vitest run --coverage"
  }
}
// vitest.config.ts
import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    environment: "node",
    globals: false,
    // 各テストの後にスパイ/モックの実装を自動で元に戻す保険
    restoreMocks: true,
    coverage: {
      provider: "v8",
      reporter: ["text", "html"],
      // include を書かないと、importされた行しか集計されない
      include: ["src/**/*.{ts,tsx}"],
      exclude: ["src/**/*.d.ts", "src/**/*.test.{ts,tsx}", "src/test/**"],
      thresholds: {
        lines: 80,
        functions: 80,
        branches: 75,
        statements: 80,
      },
    },
  },
});

globals: false にしているのは好みもありますが、理由があります。describeexpect をファイル冒頭で import する形にしておくと、Claude Codeがテストを別ファイルにコピーしたとき「expect が未定義」みたいな事故が減るんです。グローバルに頼ると、移植先で静かに壊れます。

restoreMocks: true は掃除当番の自動化です。ただし、これが面倒を見てくれるのは vi.spyOnvi.fn の実装まで。偽タイマーやDOMの掃除は別腹なので、そこは afterEach で明示します。ここを「設定で全部やってくれる」と勘違いすると、また緑なのに落ちます。

vi.mock と vi.fn、どっちを使うか問題

モックには大きく2つの道具があります。ここの使い分けが、上級者とそうでない人の分かれ目です。

やりたいこと使う道具向いている場面
依存を1個だけ偽物にするvi.fn() を引数で渡す関数に依存を外から注入できる設計
モジュールまるごと差し替えるvi.mock("./mod")importを書き換えられない、深い依存
実物は活かしつつ呼び出しを記録vi.spyOn(obj, "m")ログ出力の確認など、副作用だけ見たい

迷ったら、まず vi.fn() を引数で渡せないか検討します。vi.mock() はimport全体を差し替えられますが、後述する「巻き上げ」を理解する必要があります。依存を引数で受け取れる設計なら、偽物にする境界がコード上で見え、失敗原因も追いやすくなります。

API境界を vi.fn() で固める

外部APIを本当に叩くテストは、遅いし、ネットワークのご機嫌で落ちます。注文作成のような処理なら、APIクライアント自体ではなく「どのパスに、どんなbodyを送り、失敗をどの例外に変換するか」だけを確認すれば十分です。

// src/orders.ts
export type ApiClient = {
  post<T>(path: string, body: unknown): Promise<T>;
};

export class OrderError extends Error {
  constructor(message = "Order request failed") {
    super(message);
    this.name = "OrderError";
  }
}

type OrderInput = { sku: string; quantity: number };
type OrderResponse = { id: string; status: "accepted" | "queued" };

export async function createOrder(api: ApiClient, input: OrderInput) {
  if (input.quantity < 1) {
    throw new OrderError("Quantity must be at least 1");
  }
  try {
    return await api.post<OrderResponse>("/orders", input);
  } catch {
    throw new OrderError("Order API failed");
  }
}

テスト側では、ApiClient の偽物を vi.fn() で作って渡します。これがコピペで動く最小セットです。package.jsonscripts を入れたうえで npm run test:run を実行してください。

// src/orders.test.ts
import { describe, expect, it, vi } from "vitest";
import { createOrder, type ApiClient, OrderError } from "./orders";

describe("createOrder", () => {
  it("正常系: 注文bodyを正しいパスへ送る", async () => {
    // post を偽物にして、成功レスポンスを返させる
    const api: ApiClient = {
      post: vi.fn().mockResolvedValue({ id: "ord_1", status: "accepted" }),
    };

    await expect(createOrder(api, { sku: "book-1", quantity: 2 })).resolves.toEqual({
      id: "ord_1",
      status: "accepted",
    });
    // 何を渡して呼んだかまで検証する(ここが結合の肝)
    expect(api.post).toHaveBeenCalledWith("/orders", { sku: "book-1", quantity: 2 });
  });

  it("異常系: 数量0ならAPIを呼ぶ前に弾く", async () => {
    const api: ApiClient = { post: vi.fn() };

    await expect(
      createOrder(api, { sku: "book-1", quantity: 0 }),
    ).rejects.toBeInstanceOf(OrderError);
    // バリデーションで止まっているので post は呼ばれない
    expect(api.post).not.toHaveBeenCalled();
  });

  it("異常系: 通信エラーをドメインのエラーに包む", async () => {
    const api: ApiClient = {
      post: vi.fn().mockRejectedValue(new Error("ECONNRESET")),
    };

    await expect(createOrder(api, { sku: "book-1", quantity: 1 })).rejects.toThrow(
      "Order API failed",
    );
  });
});

成功・入力不正・通信失敗の3点セットになっているのが大事です。Claude Codeには「この3ケースを必ず含めて」と指示すれば、抜けが減ります。api.post が呼ばれた/呼ばれなかったを検証しているので、バリデーションのすり抜けにも気づけます。

vi.mock の「巻き上げ」で初期化順を間違えない

依存を引数で渡せず、import { sendMail } from "./mailer" のように直接importする関数を置き換える場合は vi.mock() を使います。次の3ファイルを同じ src ディレクトリへ置くと実行できます。

vi.mock() は、ファイルのどこに書いてもいちばん上に巻き上げられて実行されます(これはVitest公式のMockingガイドに明記されています)。つまり、ファイルの途中で定義した変数をモックの中で使おうとすると「まだ存在しない」と言われて落ちます。

// src/mailer.ts
export async function sendMail(_address: string) {
  throw new Error("real mail is disabled in tests");
}
// src/notify.ts
import { sendMail } from "./mailer";

export async function notifyUser(address: string) {
  return sendMail(address);
}
// src/notify.test.ts
import { describe, expect, it, vi } from "vitest";
import { notifyUser } from "./notify";
import { sendMail } from "./mailer";

// この vi.mock は import より前に巻き上げられて実行される
vi.mock("./mailer", () => ({
  sendMail: vi.fn().mockResolvedValue({ ok: true }),
}));

describe("notifyUser", () => {
  it("メール送信を1回だけ呼ぶ", async () => {
    await notifyUser("[email protected]");

    expect(sendMail).toHaveBeenCalledTimes(1);
    expect(sendMail).toHaveBeenCalledWith("[email protected]");
  });
});

巻き上げのため、vi.mock() のファクトリ内から、あとで初期化される通常の変数は参照できません。外の値が必要なら vi.hoisted() を使えます。ただし、依存を引数で渡せる設計へ変えたほうが、テスト対象と偽物の境界が明確になる場合もあります。

時刻を固定し、偽タイマーを必ず戻す

予約や無料トライアルのリマインダーは、実時間を待たずに検証します。次の例は日時を固定し、3日後の境界を1ミリ秒単位で確認します。

// src/trial.ts
export const DAY_MS = 24 * 60 * 60 * 1000;

export function getTrialEndsAt(days = 7) {
  return new Date(Date.now() + days * DAY_MS).toISOString();
}

export function scheduleTrialReminder(send: () => void, days = 7) {
  return setTimeout(send, days * DAY_MS);
}
// src/trial.test.ts
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { DAY_MS, getTrialEndsAt, scheduleTrialReminder } from "./trial";

describe("trial reminder", () => {
  beforeEach(() => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date("2026-07-22T00:00:00.000Z"));
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  it("固定した日時から終了日を計算する", () => {
    expect(getTrialEndsAt()).toBe("2026-07-29T00:00:00.000Z");
  });

  it("指定日数の直前には送らず、境界で1回送る", () => {
    const send = vi.fn();
    const timer = scheduleTrialReminder(send, 3);

    vi.advanceTimersByTime(3 * DAY_MS - 1);
    expect(send).not.toHaveBeenCalled();

    vi.advanceTimersByTime(1);
    expect(send).toHaveBeenCalledTimes(1);
    clearTimeout(timer);
  });
});

このテストから afterEach() を外すと、後続テストが偽の時計を引き継ぐ可能性があります。Promiseを返す処理では、タイマーを進めるだけでなく、対象のPromiseを await したかも確認します。

jsdomでは小さなDOM契約だけを確認する

jsdomはNode上でDOM APIを再現するテスト環境です。テキスト、属性、要素構造には向きますが、CSSレイアウト、実際のフォーカス移動、Canvas、見た目の崩れはPlaywrightへ分けます。

// src/notice.ts
export function renderNotice(target: HTMLElement, message: string) {
  target.innerHTML = "";

  const notice = document.createElement("p");
  notice.setAttribute("role", "status");
  notice.dataset.testid = "notice";
  notice.textContent = message;

  target.append(notice);
  return notice;
}
// src/notice.test.ts
// @vitest-environment jsdom
import { afterEach, describe, expect, it } from "vitest";
import { renderNotice } from "./notice";

afterEach(() => {
  document.body.innerHTML = "";
});

describe("renderNotice", () => {
  it("role=status の保存通知を表示する", () => {
    document.body.innerHTML = '<div id="app"></div>';
    const target = document.querySelector<HTMLDivElement>("#app");
    if (!target) throw new Error("missing #app");

    const notice = renderNotice(target, "Saved");

    expect(notice.getAttribute("role")).toBe("status");
    expect(notice.textContent).toBe("Saved");
    expect({
      html: document.body.innerHTML,
      text: notice.textContent,
    }).toMatchInlineSnapshot(`
      {
        "html": "<div id=\"app\"><p role=\"status\" data-testid=\"notice\">Saved</p></div>",
        "text": "Saved",
      }
    `);
  });
});

カバレッジは“点数”ではなく“レーダー”として読む

カバレッジ80%は合格点のように見えますが、数字だけではテストの質を判断できません。

カバレッジの本当の使い道は、自分がテストし忘れた分岐を炙り出すレーダーです。npm run coverage を走らせてHTMLレポートを開くと、通っていない行が赤くハイライトされます。そこを見て「あ、エラー時の分岐、テストしてないわ」と気づく。数字を80%に上げるためにどうでもいいテストを足すのは本末転倒です。

落とし穴が1つあります。coverage.include を書かないと、テストでimportされたファイルしか集計されません。新しく作ったけどまだ誰もテストしていないファイルは、レポートに「そもそも存在しないもの」として扱われ、カバレッジ100%なのに穴だらけ、という嘘の安心が生まれます。上の vitest.config.tsinclude: ["src/**/*.{ts,tsx}"] を明示しているのはこのためです。

しきい値(thresholds)を設定しておけば、カバレッジが基準を割った瞬間に vitest run --coverage が失敗して止まります。これをCIに入れると、テストを書かずに機能だけ足すPRに自動でブレーキがかかります。

watch と UI で“書きながら”回す

開発中は監視モード(watch)が手放せません。npm test(=vitest)で起動すると、保存するたびに関連テストだけが自動で走ります。全部走らせ直さないので速い。

もうひとつ、vitest --ui でブラウザにテスト結果のダッシュボードが出ます。どのテストが落ちたか、どのアサーションでこけたかが視覚的に見えるので、Claude Codeに直させるときの「どこが赤いか」の共有が楽になります。

# 開発中: 保存するたびに関連テストを自動実行
npm test

# ブラウザでテスト結果を見ながら回す
npx vitest --ui

Vitestの既定値は !process.env.CI && process.stdin.isTTY です。つまり、CIまたは非対話環境では vitest も単発実行になります。それでもCIのpackage scriptには vitest run(または --run)と明示すると、実行意図が読み手に伝わり、環境変数の設定ミスも調査しやすくなります。対話ツールやlint-stagedから変更関連だけを走らせる場合は vitest related --run を使えます。CI全体の組み方はClaude Code CI/CDセットアップガイドにまとめてあります。

# .github/workflows/vitest.yml
name: vitest
on:
  pull_request:
  push:
    branches: [main]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npm run test:run
      - run: npm run coverage

コピペで使えるプロンプトで落ちたテストをClaude Codeに直させる手順

ここが「advanced」の本題です。Claude Codeにテストを書かせて落ちたとき、雑に「直して」と言うと、たいていテストのほうを甘くして緑にするという最悪の直し方をされます。アサーションを消したり、expect を緩めたり。それは直したんじゃなくて、見て見ぬふりです。

修正依頼は次の順番にすると、テストを弱める変更を防ぎやすくなります。

  1. エラーの全文をそのまま貼る。 「Timeout」だけでなく、スタックトレースと、どのテストファイルのどの it で落ちたかまで渡す。vitest --ui のスクショでもいい。
  2. 「テストを緩めるな」と先に釘を刺す。 「アサーションを削らず、実装かモックの設定で直して」と明示する。
  3. 原因の仮説を1つ添える。 「他のテストと一緒に走ると落ちるなら後始末を疑って」のように方向を示すと、当てずっぽうの修正が減る。
  4. 直したあと npm run test:run を全件通すまで確認させる。 単体ではなく全件。フレーキーは単体だと再現しないからです。

実際にClaude Codeへ渡すプロンプトの型がこれです。コピペして使えます。

Vitestで src/orders.ts のテストを追加してください。
対象は createOrder のみ。外部APIは vi.fn() でモックし、本物のHTTPは呼ばないでください。
成功・入力不正・通信失敗の3ケースを必ず含めてください。
偽タイマーやjsdomが不要なら使わないでください。
テストが落ちても、アサーションを削って緑にするのは禁止です。
実装かモック設定の側で直してください。
最後に npm run test:run を全件通した想定の手順と、残るリスクを3行で報告してください。

この粒度で渡すと、Claude Codeが勝手にE2Eへ手を広げたり、設定を書き換えたりしにくくなります。チームで使うなら、CLAUDE.mdベストプラクティスに「テストを緩めて緑にするのは禁止」「外部サービスは境界を明示してモックする」「CIは vitest run」と書いておくと、毎回指示しなくても再現します。HTTPをもっと本物に近づけてモックしたいならMSWモック活用ガイド、テスト対象のAPI設計そのものはClaude Code API開発ガイドが参考になります。

3つのUse case

テストの依頼は「何を確認したいか」まで分けると、Claude Codeの出力が読みやすくなります。ここでは注文API、予約リマインダー、通知DOMという3つの現場寄りケースに分けます。いずれも本物の顧客データや課金APIは使わず、book-1 や固定日時などの合成データだけで動かします。

Use case 1: 注文APIのページ遷移前チェック

入力: skuquantity を持つ注文フォームの値、偽物の ApiClient、成功・数量0・通信失敗の3パターン。

出力: /orders に送るbody、OrderError への変換、APIを呼ばない入力不正ケースの確認結果。

人の確認: 実際の決済、請求、在庫引当は人が本番手順で承認します。Claude Codeには vi.fn() のテスト作成、失敗ログの要約、npm run test:run の再実行手順まで任せますが、料金、顧客データ、外部APIキーの扱いは人がレビューします。

このケースはEC、小売、SaaSの申込フォームで効きます。問い合わせ率やCVRを追うページほど、送信前のバリデーション抜けが売上機会の損失になります。まず注文bodyの差分をテストで固定し、次に本物のHTTP境界はMSWや契約テストへ移す順番が扱いやすいです。

Use case 2: 予約リマインダーの時刻ずれチェック

入力: 固定した Date、3日後に通知する setTimeout、送信関数の偽物。

出力: 期限のISO文字列、通知がまだ送られない境界、1ミリ秒進めた後に1回だけ送られる記録。

人の確認: Claude Codeには vi.useFakeTimers()vi.useRealTimers() の配置、境界値のテスト、残るリスクの報告を任せます。人はタイムゾーン、営業日、実際のメール送信、予約キャンセル時の扱いを確認します。

クリニック、学習塾、サロンの予約ページでは、リマインダーの送信日がずれると、来店前の案内が届かないリスクがあります。テストでは本物のメールやLINEを送らず、送信関数が呼ばれた回数だけを確認します。

Use case 3: 通知DOMのアクセシビリティ確認

入力: #app の空DOM、表示するメッセージ、role="status" を付ける小さなレンダリング関数。

出力: DOMのHTML、表示テキスト、スクリーンリーダー向け属性を含む小さなスナップショット。

人の確認: Claude Codeには jsdom のテスト、document.body.innerHTML = "" の掃除、スナップショットの最小化を任せます。人は実ブラウザのフォーカス移動、CSS崩れ、画像、Canvas、顧客に見える文言をPlaywrightや目視で承認します。

問い合わせフォーム、管理画面の保存通知、採用ページの応募完了メッセージでは、role や表示テキストが消えると、完了状態が伝わりにくくなります。Vitestは構造確認、Playwrightは実操作確認、と役割を分けます。

Claude Codeに任せる範囲と人が見る範囲

コードとコマンドの下書きはClaude Codeに寄せられます。テスト対象の洗い出し、vi.fn() の配置、afterEach の掃除、npm run test:run の失敗ログ整理、カバレッジの赤い行の要約は任せやすい作業です。

人が見る範囲は別です。セキュリティ権限、本番環境への反映、課金や決済、顧客データ、外部APIキー、CIの必須チェック化は人が承認します。Claude Codeが「通りました」と書いても、実際のコマンド結果、差分、リンク、CTA、秘密情報が混ざっていないことは人が確認してください。

項目Claude Codeに任せる人が承認する
テストコード成功・失敗ケースの下書き、モックの配置アサーションが仕様を弱めていないか
CIvitest runcoverage の手順案必須チェック化、課金時間、Nodeバージョン
データ合成データの作成顧客情報、APIキー、決済IDを入れない判断
ブラウザ確認jsdomでDOM契約を確認Playwrightや目視で本番に近い挙動を確認

Pitfall: 緑にするためにテストを弱める

テストが落ちたときの原因は、実装の抜け、モックの置き場所、タイマーの戻し忘れ、非同期の待ち忘れに分かれます。ここで「直して」とだけ頼むと、Claude Codeが expect を削る、toEqualtoBeTruthy に変える、失敗ケースをコメントアウトする、といった修正を選ぶことがあります。

直し方は先に固定します。まずエラー全文と対象ファイルを貼る。次に「アサーションを削らない」「実装かモック設定で直す」「npm run test:run を全件想定で確認する」と書く。最後に差分を見て、テストが仕様を守っているか人が確認します。今日やるなら、既存テストから toBeTruthy だけで通している箇所を1つ選び、期待するbody、ステータス、エラー名を具体的な expect に置き換えてください。

再現しやすい失敗パターン3つ

次の3つは、コードから原因と修正方法を確認できる失敗パターンです。実在する障害件数や改善率としては扱いません。

ひとつ目は、偽タイマーを戻し忘れること。リマインダーのテストで vi.useFakeTimers() を使い、同じファイルの afterEachvi.useRealTimers() を呼ばないと、後続テストが固定日時を引き継ぐ可能性があります。時計を止めたテストと戻す処理を同じ describe 内で確認します。

ふたつ目は、モックを増やしすぎること。1つのテストで複数モジュールをまとめて vi.mock() すると、失敗が実装由来か偽物由来か切り分けにくくなります。まずは「1つのユースケースで外部境界を1つ置き換える」を目安にし、純粋な計算処理は本物を通します。

みっつ目は、スナップショットを大きく取りすぎること。ページ全体を toMatchSnapshot() に入れると、関係のないclass変更まで差分になります。上の例のように、壊れると困る小さな構造だけをスナップショットへ残し、role や文言は個別の expect で確認します。

よくある質問

Q. vi.mockvi.fn() はどう使い分ければいいですか。 A. 依存を関数の引数で渡せる設計なら vi.fn() を渡すのが第一選択です。読みやすく、落ちたときの原因も短く出ます。importを直接書き換えられない深い依存だけ vi.mock を使ってください。

Q. restoreMocks: true を設定すれば後始末は全部終わりますか。 A. いいえ。それが面倒を見るのは vi.spyOn / vi.fn の実装までです。偽タイマー(vi.useFakeTimers)とDOMの掃除(document.body.innerHTML = "")は afterEach で別途やる必要があります。ここの勘違いがフレーキーの定番です。

Q. カバレッジは何%を目指せばいいですか。 A. 数字そのものを目標にしないでください。レポートの赤い行を見て「このエラー分岐、テストしてないな」と気づくための道具です。あえて基準を引くなら lines 80% / branches 75% あたりから始めて、レーダーとして使うのがおすすめです。

Q. ローカルでは通るのにCIだけ落ちます。何を疑えばいいですか。 A. まずNodeバージョンとロックファイル、次にタイマー・モック・DOMの後始末、最後にCIの環境変数と実行コマンドを確認します。CIでは意図を明確にするため vitest run を使い、同じコマンドをローカルでも再現してください。

Q. E2Eテストはこの構成に混ぜていいですか。 A. 分けたほうが幸せです。jsdomはDOMの構造確認まで、実ブラウザのクリックや見た目の崩れはPlaywright E2Eテスト実践ガイドへ。単体・結合とE2Eを同じファイルに混ぜると、遅さと不安定さが伝染します。

実際に試した結果

2026年7月22日に、記事内のTypeScript例を一時プロジェクトへ自動抽出し、Node v24.14.1とVitest 4.1.10で vitest runvitest run --coverage を実行しました。API境界、モジュールモック、固定日時、タイマー境界、DOM属性、インラインスナップショットを含む4ファイル・7テストがすべて成功しました。カバレッジはstatements 94.73%、branches 100%、functions 85.71%、lines 94.73%でした。mailer.ts はテストでモックしたため、実メールは送信していません。

あわせて、10言語の内部リンク、公式ドキュメントへの外部リンク、コードフェンス、updatedDateverifiedDate、CTAを確認しました。まず今日、自分のリポジトリで npm run test:run を実行し、成功件数または最初の失敗ファイルを確認してください。失敗ログを省略せずClaude Codeへ渡し、プロンプトには「アサーションを削らず、実装かモック設定で直す」と明記します。

自分のリポジトリに合わせてこの型を入れたい人は、プロダクト一覧に教材をまとめてあります。

#Claude Code #Vitest #モック #カバレッジ #TypeScript
無料

無料PDF: Claude Code はじめてのチートシート

まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。

スパムは送りません。登録情報は厳重に管理します。

Claude Codeを仕事で使える形にしませんか?

まず無料PDFで基本を固め、繰り返し使う作業はGumroad教材へ、チーム導入や権限設計は導入相談へ進めます。

Masa

この記事を書いた人

Masa

Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。

PR

関連書籍・参考図書

この記事のテーマに関連する書籍を楽天ブックスで探せます。

※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。