ホームページ >ウェブフロントエンド >jsチュートリアル >Jest を使用して個々のテストで関数をオーバーライドする

Jest を使用して個々のテストで関数をオーバーライドする

WBOY
WBOYオリジナル
2024-08-05 22:43:02680ブラウズ

Override functions in individual tests using Jest

一部のテストでは関数をモックしたいが、他のテストではモックしたくない場合があります。異なるテストに異なるモックを提供したい場合があります。 Jest はこれを厄介にします。そのデフォルトの動作は、単一のテストだけでなく、テスト ファイル全体のパッケージの関数をオーバーライドすることです。 Python の @patch や Laravel のサービス コンテナなどの柔軟なツールを使用したことがある場合、これは奇妙に思えます。

この投稿では、個々のテストで関数をモックし、モックが提供されていない場合は元の実装にフォールバックする方法を示します。 CommonJS モジュールと ES モジュールの両方の例が示されます。この投稿で説明した手法は、ファーストパーティのモジュールとサードパーティのパッケージの両方で機能します。

CommonJS と ES モジュールの比較

この投稿では複数のモジュール システムについて説明するため、それらが何であるかを理解することが重要です。

CommonJS (略称 CJS) は、Node.js のモジュール システムです。 module.exports を使用して関数をエクスポートし、require():
を使用して関数をインポートします。

// CommonJS export 

function greet() {
  return "Hello, world!";
}

module.exports = { greet };
// CommonJS import

const getUsersList = require('./greet');

ES モジュール (略称 ESM) は、ブラウザーで使用されるモジュール システムです。これは、export キーワードを使用して関数をエクスポートし、import キーワードを使用して関数をインポートします。

// ES module export

export default function greet() {
  return "Hello, world!";
}
// ES module import

import { greet } from "./greet";

この記事の執筆時点では、ほとんどのフロントエンド JavaScript 開発者が ES モジュールを使用しており、多くのサーバーサイド JS 開発者も同様に ES モジュールを使用しています。ただし、Node のデフォルトは依然として CommonJS です。どのシステムを使用するかに関係なく、Jest のモッキング システムについて学ぶために記事全体を読む価値があります。

CommonJS を使用して単一のエクスポート関数をモックする

通常、CommonJS ファイルは、以下に示すようなオブジェクト構文を使用してモジュールをエクスポートします。

// CommonJS export 

function greet() {
  return "Hello, world!";
}

module.exports = { greet: greet };

ただし、関数を単独でエクスポートすることも可能です。

// CommonJS export 

function greet() {
  return "Hello, world!";
}

module.exports = greet;

これを独自のコードで行うことは必ずしもお勧めしません。 オブジェクトをエクスポートすると、アプリケーションの開発中に頭の痛い問題が軽減されます。ただし、これはよくあることなので、CommonJS でエクスポートされた裸の関数をモックし、テストで独自の実装が提供されていない場合は元の関数にフォールバックする方法を議論する価値があります。

テスト中にモックしたい次の CommonJS ファイルがあるとします:

// cjsFunction.js

function testFunc() {
  return "original";
}

module.exports = testFunc;

次のコードを使用してテストでモックできます:

const testFunc = require("./cjsFunction");

jest.mock("./cjsFunction");

beforeEach(() => {
  testFunc.mockImplementation(jest.requireActual("./cjsFunction"));
});

it("can override the implementation for a single test", () => {
  testFunc.mockImplementation(() => "mock implementation");

  expect(testFunc()).toBe("mock implementation");
  expect(testFunc.mock.calls).toHaveLength(1);
});

it("can override the return value for a single test", () => {
  testFunc.mockReturnValue("mock return value");

  expect(testFunc()).toBe("mock return value");
  expect(testFunc.mock.calls).toHaveLength(1);
});

it("returns the original implementation when no overrides exist", () => {
  expect(testFunc()).toBe("original");
  expect(testFunc.mock.calls).toHaveLength(1);
});

仕組み

jest.mock("./cjsFunction") を呼び出すと、モジュール (ファイルとそのすべてのエクスポート) が自動モック (ドキュメント) に置き換えられます。 auto-mock が呼び出されると、unknown が返されます。ただし、モックの実装、戻り値などをオーバーライドするためのメソッドが提供されます。 Jest Mock Functions のドキュメントで、Jest Mock Functions が提供するすべてのプロパティとメソッドを確認できます。

モックのmockImplementation()メソッドを使用して、モックの実装を元のモジュールの実装に自動的に設定できます。 Jest は、現在モックされている場合でも、常に元のモジュールをロードする jest.requireActual() メソッドを提供します。

モック実装と戻り値は各テスト後に自動的にクリアされるため、各テストの前にモックの実装を元の実装に設定するコールバック関数を Jest の beforeEach() 関数に渡すことができます。その後、独自の戻り値または実装を提供したいテストは、テスト本体内で手動で行うことができます。

オブジェクトのエクスポート時に CommonJS をモックする

上記のコードが単一の関数ではなくオブジェクトをエクスポートしたとします。

// cjsModule.js

function testFunc() {
  return "original";
}

module.exports = {
  testFunc: testFunc,
};

テストは次のようになります:

const cjsModule = require("./cjsModule");

afterEach(() => {
  jest.restoreAllMocks();
});

it("can override the implementation for a single test", () => {
  jest
    .spyOn(cjsModule, "testFunc")
    .mockImplementation(() => "mock implementation");

  expect(cjsModule.testFunc()).toBe("mock implementation");
  expect(cjsModule.testFunc.mock.calls).toHaveLength(1);
});

it("can override the return value for a single test", () => {
  jest.spyOn(cjsModule, "testFunc").mockReturnValue("mock return value");

  expect(cjsModule.testFunc()).toBe("mock return value");
  expect(cjsModule.testFunc.mock.calls).toHaveLength(1);
});

it("returns the original implementation when no overrides exist", () => {
  expect(cjsModule.testFunc()).toBe("original");
});

it("can spy on calls while keeping the original implementation", () => {
  jest.spyOn(cjsModule, "testFunc");

  expect(cjsModule.testFunc()).toBe("original");
  expect(cjsModule.testFunc.mock.calls).toHaveLength(1);
});

仕組み

jest.spyOn() メソッドを使用すると、Jest がオブジェクトのメソッドへの呼び出しを記録し、独自の置換を提供できるようになります。これはオブジェクトに対してのみ で動作し、モジュールが関数を含むオブジェクトをエクスポートしているため、これを使用できます。

spyOn() メソッドはモックであるため、その状態をリセットする必要があります。 Jest spyOn() ドキュメントでは、afterEach() コールバックで jest.restoreAllMocks() を使用して状態をリセットすることを推奨しています。これは上記で行ったことです。これを行わなかった場合、モックは spyOn() が呼び出された後の次のテストで未定義を返します。

ES モジュールのモック化

ES モジュールにはデフォルトのエクスポートと名前付きエクスポートを含めることができます:

// esmModule.js

export default function () {
  return "original default";
}

export function named() {
  return "original named";
}

上記のファイルのテストは次のようになります:

import * as esmModule from "./esmModule";

afterEach(() => {
  jest.restoreAllMocks();
});

it("can override the implementation for a single test", () => {
  jest
    .spyOn(esmModule, "default")
    .mockImplementation(() => "mock implementation default");
  jest
    .spyOn(esmModule, "named")
    .mockImplementation(() => "mock implementation named");

  expect(esmModule.default()).toBe("mock implementation default");
  expect(esmModule.named()).toBe("mock implementation named");

  expect(esmModule.default.mock.calls).toHaveLength(1);
  expect(esmModule.named.mock.calls).toHaveLength(1);
});

it("can override the return value for a single test", () => {
  jest.spyOn(esmModule, "default").mockReturnValue("mock return value default");
  jest.spyOn(esmModule, "named").mockReturnValue("mock return value named");

  expect(esmModule.default()).toBe("mock return value default");
  expect(esmModule.named()).toBe("mock return value named");

  expect(esmModule.default.mock.calls).toHaveLength(1);
  expect(esmModule.named.mock.calls).toHaveLength(1);
});

it("returns the original implementation when no overrides exist", () => {
  expect(esmModule.default()).toBe("original default");
  expect(esmModule.named()).toBe("original named");
});

仕組み

これは、前の CommonJS の例とほぼ同じように見えますが、いくつかの重要な違いがあります。

まず、モジュールを名前空間インポートとしてインポートします。

import * as esmModule from "./esmModule";

デフォルトのエクスポートを監視したい場合は、「default」を使用します:

  jest
    .spyOn(esmModule, "default")
    .mockImplementation(() => "mock implementation default");

Troubleshooting ES module imports

Sometimes when trying to call jest.spyOn() with a third-party package, you'll get an error like the one below:

    TypeError: Cannot redefine property: useNavigate
        at Function.defineProperty (<anonymous>)

When you run into this error, you'll need to mock the package that is causing the issue:

import * as reactRouterDOM from "react-router-dom";

// ADD THIS:
jest.mock("react-router-dom", () => {
  const originalModule = jest.requireActual("react-router-dom");

  return {
    __esModule: true,
    ...originalModule,
  };
});

afterEach(() => {
  jest.restoreAllMocks();
});

This code replaces the module with a Jest ES Module mock that contains all of the module's original properties using jest.mocks's factory parameter. The __esModule property is required whenever using a factory parameter in jest.mock to mock an ES module (docs).

If you wanted, you could also replace an individual function in the factory parameter. For example, React Router will throw an error if a consumer calls useNavigate() outside of a Router context, so we could use jest.mock() to replace that function throughout the whole test file if we desired:

jest.mock("react-router-dom", () => {
  const originalModule = jest.requireActual("react-router-dom");

  return {
    __esModule: true,
    ...originalModule,

    // Dummy that does nothing.
    useNavigate() {
      return function navigate(_location) {
        return;
      };
    },
  };
});

Wrapping up

I hope this information is valuable as you write your own tests. Not every app will benefit from being able to fallback to the default implementation when no implementation is provided in a test itself. Indeed, many apps will want to use the same mock for a whole testing file. However, the techniques shown in this post will give you fine-grained control over your mocking.

Let me know if I missed something or if there's something that I didn't include in this post that should be here.

以上がJest を使用して個々のテストで関数をオーバーライドするの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。