--- title: "「not a constructor」エラー解決 - Vitestでクラスベースライブラリをモックする方法" created: "2026-01-01" description: "Vitestで「not a constructor」エラーが出た時の解決方法を解説。JSZipなどクラスベースライブラリを`new`キーワードで正しくモックする方法をコード例と共に紹介。テストが失敗する原因と修正方法がわかります。" status: "published" platforms: wordpress: published: true url: "https://kurokawa.dev/testing/vitest-jszip-mock-class-based-library" published_date: "2026-01-26" category: "testing/vitest" tags: ["Vitest", "クラスモック", "JSZip", "not a constructor", "単体テスト", "TypeScript", "vi.mock"] --- ## Vitestでクラスベースライブラリのモックに躓いた背景 画像ダウンロード機能のユニットテストを作成中に、JSZipライブラリのモックでテストが失敗する問題に遭遇しました。 正直なところ、このエラー原因の特定に予想以上に時間がかかりましたが、解決できたおかげでテスト実行が安定して通りやすくなりました。 本記事では、実際の開発現場で遭遇した「not a constructor」エラーの具体的な解決策をコード例と共に解説します。 ### こんな人におすすめ - Vitestでクラスベースライブラリをモックしたい方 - 「not a constructor」エラーで困っている開発者 - JSZipのテストを書いているエンジニア - `new` キーワードで呼び出すライブラリのモック方法を知りたい方 ## 目次 - [Vitestでクラスベースライブラリのモックに躓いた背景](#vitestでクラスベースライブラリのモックに躓いた背景) - [「not a constructor」エラーの症状](#not-a-constructorエラーの症状) - [Vitestでクラスベースライブラリのモックが失敗する原因](#vitestでクラスベースライブラリのモックが失敗する原因) - [【解決策】Vitestでクラスを正しくモックする方法](#解決策vitestでクラスを正しくモックする方法) - [モック方法の選択ガイド](#モック方法の選択ガイド) - [クラスベースのモック導入で得られた効果](#クラスベースのモック導入で得られた効果) - [Vitestでクラスモックする際のポイントまとめ](#vitestでクラスモックする際のポイントまとめ) - [確認手順](#確認手順) ## 「not a constructor」エラーの症状 テスト実行時に、JSZipのインスタンス化でエラーが発生しました。 ``` TypeError: () => mockZip is not a constructor ``` このエラーメッセージからは、モックの定義に問題があることが読み取れませんでした。 実際にデバッグに時間を要しましたが、原因はモックの定義方法にありました。 ## Vitestでクラスベースライブラリのモックが失敗する原因 JSZipは `new JSZip()` でインスタンス化するクラスベースのライブラリです。 Vitestで `vi.fn(() => mockZip)` を使うと、関数として呼び出されることを期待してしまい、`new` キーワードでのコンストラクタ呼び出しに対応できません。 ### NG: 関数ベースのモック(失敗例) ```typescript vi.mock('jszip', () => { const mockZip = { file: vi.fn(), generateAsync: vi.fn().mockResolvedValue(new Blob(['mock'])), }; return { default: vi.fn(() => mockZip) }; }); ``` このモック定義では、`vi.fn()` で作成された関数を返しているため、`new` キーワードでの呼び出し時に「not a constructor」エラーが発生します。 **なぜ失敗するのか**: `vi.fn()` は関数オブジェクトを作成しますが、これは `new` キーワードでコンストラクタとして呼び出すことはできません。 以下のシーケンス図で、関数ベースのモックが失敗する流れを確認できます。 ```mermaid sequenceDiagram participant Test as テストコード participant Vitest as vi.mock() participant Mock as vi.fn()(関数) participant Error as TypeError Test->>Vitest: vi.mock('jszip', ...) を定義 Vitest->>Mock: vi.fn(() => mockZip) を返す Note over Mock: 関数オブジェクト(コンストラクタではない) Test->>Mock: new JSZip() を呼び出し Mock-->>Error: ❌ "not a constructor" Note over Error: new キーワードで<br/>関数をインスタンス化できない ``` ## 【解決策】Vitestでクラスを正しくモックする方法 モックを**クラス定義**に変更します。 ### OK: クラスベースのモック(正解例) ```typescript vi.mock('jszip', () => { return { default: class MockJSZip { file = vi.fn(); generateAsync = vi.fn().mockResolvedValue(new Blob(['mock zip content'])); }, }; }); ``` このモック定義では、クラス構文を使ってモックを定義しているため、`new` キーワードでの呼び出しに正しく対応できます。 **ポイント**: - `class` 構文を使ってモックを定義する - クラスプロパティとして `vi.fn()` を設定する - `default` エクスポートにクラスを返す 以下のシーケンス図で、クラスベースのモックが成功する流れを確認できます。 ```mermaid sequenceDiagram participant Test as テストコード participant Vitest as vi.mock() participant Mock as class MockJSZip participant Methods as モックメソッド Test->>Vitest: vi.mock('jszip', ...) を定義 Vitest->>Mock: class MockJSZip を返す Note over Mock: クラス定義(コンストラクタとして機能) Test->>Mock: new JSZip() を呼び出し Mock-->>Test: ✅ インスタンスを返す Test->>Methods: zip.file() を呼び出し Methods-->>Test: モックされた振る舞いを返す Test->>Methods: zip.generateAsync() を呼び出し Methods-->>Test: mockResolvedValue を返す Note over Methods: クラスプロパティとして<br/>vi.fn() が設定されている ``` ### Vitestクラスモックの基本パターン クラスベースのライブラリをモックする際の基本パターンは以下の通りです。 ```typescript vi.mock('ライブラリ名', () => ({ default: class MockClassName { メソッド名 = vi.fn(); プロパティ名 = 値; }, })); ``` このパターンはJSZip以外にも、以下のようなクラスベースライブラリで活用できます。 - ファイル操作系ライブラリ(FileSaver.jsなど) - 画像処理系ライブラリ - APIクライアント系ライブラリ ### モック方法の選択ガイド ライブラリの特性に応じて、適切なモック方法を選択するフローチャートです。 ```mermaid flowchart TD A[ライブラリをモックする] --> B{new キーワードで<br/>呼び出すか?} B -->|はい| C[クラスベースのモック] B -->|いいえ| D[関数ベースのモック] C --> E{default exportか?} E -->|はい| F["return: default = class MockClass"] E -->|いいえ| G["return: ClassName = class MockClass"] D --> H{default exportか?} H -->|はい| I["vi.fn または<br/>vi.fnでreturnValueを返す"] H -->|いいえ| J["methodName: vi.fnを設定"] F --> K[✅ new ライブラリ名 で呼び出し可能] G --> K I --> L[✅ 通常の関数呼び出しで使用] J --> L style C fill:#90EE90 style D fill:#87CEEB style K fill:#FFD700 style L fill:#FFD700 ``` **使い方**: 1. ライブラリのドキュメントで `new` キーワードの使用を確認 2. 上記フローチャートに従ってモック方法を選択 3. 適切な `vi.mock()` パターンを適用 ## クラスベースのモック導入で得られた効果 クラスベースのモックに変更後、以下の効果が得られました。 - テスト実行が安定して通りやすくなりました - テストコードが実際のJSZipの使用方法に近くなり、可読性が向上しました - 他のクラスベースライブラリ(例:ファイル操作系ライブラリ)のモックにも同様のパターンを適用できるようになりました 正しいモック方法のおかげで、テスト実行時間が短縮され、デバッグ効率が向上しました。 ## Vitestでクラスモックする際のポイントまとめ クラスベースライブラリをモックする際は、以下の点に注意が必要です。 - **モックもクラスとして定義する必要があります** - `vi.fn()` ではなく `class` 構文を使用 - **`new` キーワードで呼び出されるライブラリは、関数ではなくクラス構文でモックを書きます** - **VitestのES Module モックでは `default` エクスポートに注意します** - 名前付きエクスポートの場合は適切に調整 ### 関連するVitestのトラブルシューティング Vitestでのテスト中に他の問題が発生した場合は、以下の記事も参考にしてください。 - [Vitest fake timersとReact Testing Library waitForの相性問題と解決策](https://wakatchi.dev/vitest-fake-timers-react-testing-library-pitfalls/) - `waitFor` がタイムアウトする問題の解決方法 ## 確認手順 1. `npm run test -- tests/utils/downloadImage.test.ts` でテストを実行します 2. 全テストがパスすることを確認します

Vitestでクラスベースライブラリのモックに躓いた背景

画像ダウンロード機能のユニットテストを作成中に、JSZipライブラリのモックでテストが失敗する問題に遭遇しました。

正直なところ、このエラー原因の特定に予想以上に時間がかかりましたが、解決できたおかげでテスト実行が安定して通りやすくなりました。

本記事では、実際の開発現場で遭遇した「not a constructor」エラーの具体的な解決策をコード例と共に解説します。

こんな人におすすめ

  • Vitestでクラスベースライブラリをモックしたい方
  • 「not a constructor」エラーで困っている開発者
  • JSZipのテストを書いているエンジニア
  • new キーワードで呼び出すライブラリのモック方法を知りたい方

目次

「not a constructor」エラーの症状

テスト実行時に、JSZipのインスタンス化でエラーが発生しました。

TypeError: () => mockZip is not a constructor

このエラーメッセージからは、モックの定義に問題があることが読み取れませんでした。

実際にデバッグに時間を要しましたが、原因はモックの定義方法にありました。

Vitestでクラスベースライブラリのモックが失敗する原因

JSZipは new JSZip() でインスタンス化するクラスベースのライブラリです。

Vitestで vi.fn(() => mockZip) を使うと、関数として呼び出されることを期待してしまい、new キーワードでのコンストラクタ呼び出しに対応できません。

NG: 関数ベースのモック(失敗例)

vi.mock('jszip', () => {
  const mockZip = {
    file: vi.fn(),
    generateAsync: vi.fn().mockResolvedValue(new Blob(['mock'])),
  };
  return { default: vi.fn(() => mockZip) };
});

このモック定義では、vi.fn() で作成された関数を返しているため、new キーワードでの呼び出し時に「not a constructor」エラーが発生します。

なぜ失敗するのか: vi.fn() は関数オブジェクトを作成しますが、これは new キーワードでコンストラクタとして呼び出すことはできません。

以下のシーケンス図で、関数ベースのモックが失敗する流れを確認できます。

sequenceDiagram
    participant Test as テストコード
    participant Vitest as vi.mock()
    participant Mock as vi.fn()(関数)
    participant Error as TypeError

    Test->>Vitest: vi.mock('jszip', ...) を定義
    Vitest->>Mock: vi.fn(() => mockZip) を返す
    Note over Mock: 関数オブジェクト(コンストラクタではない)

    Test->>Mock: new JSZip() を呼び出し
    Mock-->>Error: ❌ "not a constructor"

    Note over Error: new キーワードで<br/>関数をインスタンス化できない

【解決策】Vitestでクラスを正しくモックする方法

モックをクラス定義に変更します。

OK: クラスベースのモック(正解例)

vi.mock('jszip', () => {
  return {
    default: class MockJSZip {
      file = vi.fn();
      generateAsync = vi.fn().mockResolvedValue(new Blob(['mock zip content']));
    },
  };
});

このモック定義では、クラス構文を使ってモックを定義しているため、new キーワードでの呼び出しに正しく対応できます。

ポイント:

  • class 構文を使ってモックを定義する
  • クラスプロパティとして vi.fn() を設定する
  • default エクスポートにクラスを返す

以下のシーケンス図で、クラスベースのモックが成功する流れを確認できます。

sequenceDiagram
    participant Test as テストコード
    participant Vitest as vi.mock()
    participant Mock as class MockJSZip
    participant Methods as モックメソッド

    Test->>Vitest: vi.mock('jszip', ...) を定義
    Vitest->>Mock: class MockJSZip を返す
    Note over Mock: クラス定義(コンストラクタとして機能)

    Test->>Mock: new JSZip() を呼び出し
    Mock-->>Test: ✅ インスタンスを返す

    Test->>Methods: zip.file() を呼び出し
    Methods-->>Test: モックされた振る舞いを返す
    Test->>Methods: zip.generateAsync() を呼び出し
    Methods-->>Test: mockResolvedValue を返す

    Note over Methods: クラスプロパティとして<br/>vi.fn() が設定されている

Vitestクラスモックの基本パターン

クラスベースのライブラリをモックする際の基本パターンは以下の通りです。

vi.mock('ライブラリ名', () => ({
  default: class MockClassName {
    メソッド名 = vi.fn();
    プロパティ名 = 値;
  },
}));

このパターンはJSZip以外にも、以下のようなクラスベースライブラリで活用できます。

  • ファイル操作系ライブラリ(FileSaver.jsなど)
  • 画像処理系ライブラリ
  • APIクライアント系ライブラリ

モック方法の選択ガイド

ライブラリの特性に応じて、適切なモック方法を選択するフローチャートです。

flowchart TD
    A[ライブラリをモックする] --> B{new キーワードで<br/>呼び出すか?}

    B -->|はい| C[クラスベースのモック]
    B -->|いいえ| D[関数ベースのモック]

    C --> E{default exportか?}
    E -->|はい| F["return: default = class MockClass"]
    E -->|いいえ| G["return: ClassName = class MockClass"]

    D --> H{default exportか?}
    H -->|はい| I["vi.fn または<br/>vi.fnでreturnValueを返す"]
    H -->|いいえ| J["methodName: vi.fnを設定"]

    F --> K[✅ new ライブラリ名 で呼び出し可能]
    G --> K
    I --> L[✅ 通常の関数呼び出しで使用]
    J --> L

    style C fill:#90EE90
    style D fill:#87CEEB
    style K fill:#FFD700
    style L fill:#FFD700

使い方:

  1. ライブラリのドキュメントで new キーワードの使用を確認
  2. 上記フローチャートに従ってモック方法を選択
  3. 適切な vi.mock() パターンを適用

クラスベースのモック導入で得られた効果

クラスベースのモックに変更後、以下の効果が得られました。

  • テスト実行が安定して通りやすくなりました
  • テストコードが実際のJSZipの使用方法に近くなり、可読性が向上しました
  • 他のクラスベースライブラリ(例:ファイル操作系ライブラリ)のモックにも同様のパターンを適用できるようになりました

正しいモック方法のおかげで、テスト実行時間が短縮され、デバッグ効率が向上しました。

Vitestでクラスモックする際のポイントまとめ

クラスベースライブラリをモックする際は、以下の点に注意が必要です。

  • モックもクラスとして定義する必要があります - vi.fn() ではなく class 構文を使用
  • new キーワードで呼び出されるライブラリは、関数ではなくクラス構文でモックを書きます
  • VitestのES Module モックでは default エクスポートに注意します - 名前付きエクスポートの場合は適切に調整

関連するVitestのトラブルシューティング

Vitestでのテスト中に他の問題が発生した場合は、以下の記事も参考にしてください。

確認手順

  1. npm run test -- tests/utils/downloadImage.test.ts でテストを実行します
  2. 全テストがパスすることを確認します