Jestとのインポート機能を模擬する方法


📣 Notice
This article is part of a series:

Testing JavaScript with Jest

To get the most out of this article, I recommend reading the previous article in the series:


依存関係を持つコードの書き込み単位のテストは困難である.これは特に、我々がテストする必要があるすべてのケースをカバーする付属品を思い付くためにトリッキーな大きなコードベースの場合はtrueです.
しかし、関数依存の戻り値を制御することができれば、どんな引数が呼び出されてもどうですか?
これはmock関数が入るところです.
MOCK関数は、関数依存性が呼び出され、返り値を制御する方法を追跡するテストツールです.これにより、テストプログラムの制御フローを操作することができ、テストを書く際にそれらの困難なエッジの場合にも到達できます.
本論文では、モッキングの背後にある概念と、それがどのように単体テストに関連するかを紹介する.私たちはどのように機能し、Jestを使用して関数のインポートモジュールを学び、それらのテストケースのカバレッジを高めるためにそれらの模擬に依存してテストを書く.
検証規則のいくつかをテストしていると仮定します.
// isInteger.js
module.exports = (value) => Number.isSafeInteger(value);
// isAtLeast18.js
const isInteger = require("./isInteger");

module.exports = (value) => isInteger(value) && value >= 18;
我々のテストがテストケースを通過して、失敗することによって我々のコードの欠陥について我々に教えるものを見たいです.実装を修正することは、この記事ではカバーされていないが、私たちが記事を移動すると、それを再生する自由を感じる.
詳細を見つけるために読んでください!

どのようにJestとインポート機能を模擬するには?
Jestを使ってインポート機能を模擬するにはjest.mock() function .jest.mock() が必要な引数で呼び出されます.オプションの2番目の引数- mockのファクトリ関数で呼び出すこともできます.ファクトリ関数が提供されていない場合、Jestはimportモジュールをautomockします.

💡Note
Jest automock is the automatic mocking of imported modules with surface-level replacement implementations. Automocking is disabled by default since Jest 15, but can be enabled by configuring Jest with the automock flag.


テスト時isAtLeast18() 我々はそのことを心に留めなければならないisInteger() 依存関係はモジュールの振る舞いに影響を及ぼします:
  • if isInteger() is false , isAtLeast18() is false ;
  • if isInteger() is true , isAtLeast18() 値引数に依存します.
  • のケースをテストすることから始めましょうisInteger() 帰着false .
    The isInteger.js モジュールは単一のデフォルトエクスポートを行いますisInteger() 関数.インポートされたモジュールをデフォルトのエクスポートと同じように動作し、関数を返すファクトリ関数を使用します.この関数は、コールされると常にfalse .
    // isAtLeast18.spec.js
    const isAtLeast18 = require("./isAtLeast18");
    
    // The mock factory returns the function () => false
    jest.mock("./isInteger", () => () => false);
    
    describe("isAtLeast18", () => {
        it("fails if value is not recognised as integer", () => {
            // Should pass, but fails because of the isInteger() mock
            expect(isAtLeast18(123)).toBe(false);
            // Should fail either way
            expect(isAtLeast18("abc")).toBe(false);
        });
    });
    

    💡Note
    The import path of the mocked module must match the import path that is present in the module we're testing. The isAtLeast18.js module imports the isInteger.js module under the path "./isInteger". This is why our mock import path is also "./isInteger".

    isAtLeast18() は常にfalse 何を私たちがそれを呼び出してもisInteger() MOCKは常に戻るように設定されますfalse .
    しかし、ときはどうですかisInteger() リターンtrue ?
    テストによって異なる戻り値を模擬するには、mock関数を作成します.

    💡Note
    This unit test is a solitary unit test because the tested unit is isolated from its dependencies. Read more about solitary unit tests in the previous article: .



    モック関数とは
    mock関数は、関数の実際の実装を「偽」(mock)実装で置き換える機能です.
    mock関数は外部コードでどのように呼び出されるかを追跡します.mock関数を使用すると、関数が呼び出された回数、引数が呼び出された引数、返された結果、および多くを知ることができます.関数呼び出しで「スパイ」するこの機能は、なぜmock関数もスパイと呼ばれます.
    私たちはカスタムモックの実装を使用して元の関数の動作をオーバーライドするためにmock関数を使用します.模擬実装では、関数の戻り値を制御できます.これは、我々のテストをより予測可能(決定的)にし、書くことが容易になります.

    どのようにジェストと機能を模擬するには?
    Jestで機能を模擬するにはjest.fn() function .jest.fn() 実装関数を任意の引数として呼び出すことができます.実装が提供されるならば、MOCK機能を呼ぶことは実装を呼び出して、それが戻り値であることを返します.
    実装が全く提供されないならば、mockundefined 返り値が定義されていないためです.
    // Without implementation, this mock returns `undefined`.
    const mockUndefined = jest.fn();
    
    // With implementation, this mock returns `true`.
    const mockTrue = jest.fn(() => true).
    
    jest register mockはデフォルトで"jest . fn () "nameの下で機能します.私たちはモック関数にカスタム名を与えることができますmockName() method . mock nameはテスト結果を印刷する際に使用されます.
    const mockOne = jest.fn(() => false);
    // Example error: expect(jest.fn()).toHaveBeenCalledWith(...expected)
    
    const mockTwo = jest.fn(() => false).mockName('mockTwo');
    // Example error: expect(mockTwo).toHaveBeenCalledWith(...expected)
    

    💡Note
    It's good practice to name mocked functions in cases where a lot of different mocks are used. This makes it easier to tell mocked functions apart and debug code that isn't matching expectations.



    Jestで機能の模擬実装を変える方法?
    Jestで機能の模擬実装を変更するにはmockImplementation() method モンク関数の.
    The mockImplementation() メソッドは新しい実装を引数として呼び出されます.その後、モックが呼ばれるとき、新しい実装は前のものの代わりに使われます.
    // The initial mock is a function that returns `true`.
    const myMock = jest.fn(() => true);
    
    // The new mock implementation has the function return `false`.
    myMock.mockImplementation(() => false);
    
    これを組み合わせることができますjest.mock() 工場関数はmockked関数を含むmockkedモジュールを作成します.この方法では、我々がテストしているものによって、モックの実装がどのように振る舞うかを制御することができます.
    // isAtLeast18.spec.js
    const isAtLeast18 = require("./isAtLeast18");
    const isInteger = require("./isInteger");
    
    // The mock factory returns a mocked function
    jest.mock("./isInteger", () => jest.fn());
    
    describe("isAtLeast18", () => {
        it("fails if value is not recognised as integer", () => {
            // For this test we'll mock isInteger to return `false`
            isInteger.mockImplementation(() => false);
    
            expect(isAtLeast18(123)).toBe(false);
            expect(isAtLeast18("abc")).toBe(false);
        });
    
        it("passes if value is recognised as integer and is at least 18", () => {
            // For this test we'll mock isInteger to return `true`
            isInteger.mockImplementation(() => true);
    
            expect(isAtLeast18(123)).toBe(true);
            expect(isAtLeast18("abc")).toBe(false);
        });
    });
    

    💡Note
    jest.mock() works by modifying the Node module cache to give us the mock instead of the original implementation whenever we import a mocked module in a test file. To support ES module imports - where import statements have to come first in a file - Jest automatically hoists jest.mock() calls to the top of the module. Read more about this technique here.



    関数がJestで正しく呼び出されたかどうかを確認するには?
    Jestを使って関数が正しく呼び出されたかどうかをチェックするにはexpect() function 特定のMatcherメソッドを使用してアサーションを作成します.
    私たちはtoHaveBeenCalledWith() matcher method 引数をアサートするには、mockked関数が呼び出されました.
    mockked関数が呼び出された回数をアサートするには、toHaveBeenCalledTimes() matcher method .
    // isAtLeast18.spec.js
    const isAtLeast18 = require("./isAtLeast18");
    const isInteger = require("./isInteger");
    
    jest.mock("./isInteger", () => jest.fn());
    
    describe("isAtLeast18", () => {
      it("fails if value is not recognised as integer", () => {
        isInteger.mockImplementation(() => false);
    
        expect(isAtLeast18(123)).toBe(false);
    
            // We expect isInteger to be called with 123
        expect(isInteger).toHaveBeenCalledWith(123);
            // We expect isInteger to be called once
        expect(isInteger).toHaveBeenCalledTimes(1);
      });
    });
    

    💡Note
    While these are the most common matcher methods for functions, there are more matcher methods available in the Jest API docs.


    Jestはmockked関数へのすべての呼び出しを追跡します.mockked関数はarguments and times これはresults それらの呼び出しの.
    テストの間のmockked関数を再利用するとき、それは明確なベースラインを得るために新しいテストを実行する前にそれらの状態をリセットするのに役に立ちます.テストの間のmockkedされた機能をクリアすることによって、それをすることができます.

    どのように冗談を使用してmockked機能をクリアするには?
    Jestを使ってmockked関数をクリアするにはmockClear() method モンク関数の.mockClear() モックアップされた関数に格納されているすべての情報をリセットします.これは、アサーションまたはテストの間の模擬使用データをクリーンアップするのに役立ちます.
    // isAtLeast18.spec.js
    const isAtLeast18 = require("./isAtLeast18");
    const isInteger = require("./isInteger");
    
    jest.mock("./isInteger", () => jest.fn());
    
    describe("isAtLeast18", () => {
      it("fails if value is not recognised as integer", () => {
        isInteger.mockImplementation(() => false);
    
        expect(isAtLeast18(123)).toBe(false);
        expect(isInteger).toHaveBeenCalledWith(123);
        expect(isInteger).toHaveBeenCalledTimes(1);
    
            // Clear the mock so the next test starts with fresh data
        isInteger.mockClear();
      });
    
      it("passes if value is recognised as integer and is at least 18", () => {
        isInteger.mockImplementation(() => true);
    
        expect(isAtLeast18(123)).toBe(true);
        expect(isInteger).toHaveBeenCalledWith(123);
            // Without clearing, there would be 2 calls total at this point
        expect(isInteger).toHaveBeenCalledTimes(1);
      });
    });
    

    💡Note
    Jest also provides mock function methods for resetting and restoring mocked functions, as well as shorthands for creating mocked functions that directly return, resolve, or reject a value.



    どのようにJestと各テストの前にmockked機能をクリアするには?
    Jestで各テストの前にmockked関数をクリアするにはbeforeEach() function .beforeEach() は必須の引数と呼ばれ、テストファイルのテスト前に実行される.私たちはクリアモックには、フィクスチャを設定するか、またはテストに使用されるいくつかの他の状態をリセットするために使用します.
    // isAtLeast18.spec.js
    const isAtLeast18 = require("./isAtLeast18");
    const isInteger = require("./isInteger");
    
    jest.mock("./isInteger", () => jest.fn());
    
    // Clear mock data before each test
    beforeEach(() => {
      isInteger.mockClear();
    });
    
    describe("isAtLeast18", () => {
      it("fails if value is not recognised as integer", () => {
        isInteger.mockImplementation(() => false);
    
        expect(isAtLeast18(123)).toBe(false);
        expect(isInteger).toHaveBeenCalledWith(123);
        expect(isInteger).toHaveBeenCalledTimes(1);
      });
    
      it("passes if value is recognised as integer and is at least 18", () => {
        isInteger.mockImplementation(() => true);
    
        expect(isAtLeast18(123)).toBe(true);
        expect(isInteger).toHaveBeenCalledWith(123);
        expect(isInteger).toHaveBeenCalledTimes(1);
      });
    });
    

    💡Note
    Jest provides four functions to hook into the set-up and tear-down process, both before and after each or all of the tests in a test file. These functions are: afterAll(), afterEach(), beforeAll(), beforeEach(). The afterAll() and beforeAll() variants are called only once for the entire test file. The afterEach() and beforeEach() variants are called once for every test in the test file.



    どのように冗談を使用してモックを再利用するには?
    冗談でもてあそびを再利用するために、我々は__mocks__/ subdirectory モジュールに隣接して、我々はモックしたいです.
    モックファイル__mocks__/ サブディレクトリは、モジュールがモックされたときに隣接するモジュールをautomockするために使用されますjest.mock() . これは、モックスファクトリ機能を不要にするため、一般的な依存関係やコンフィギュレーションオブジェクトをモッキングするときなどのモックを設定する際に多くの繰り返しを扱うときに便利です.
    多くの異なるモジュールが使用する一般的な設定ファイルを想定して、以下のようになります.
    // common/config.js
    module.exports = { foo: "bar" };
    
    // common/__mocks__/config.js
    module.exports = { foo: "mockBar" };
    
    // example.js
    const config = require.("./common/config");
    
    // Logs "bar"
    module.exports = () => console.log(config.foo);
    
    // example.spec.js
    const example = require("./example");
    
    jest.mock("./common/config");
    
    // Logs "mockBar", no need for a mock factory
    example();
    

    💡Note
    Remember: mocks and automocking are only in effect when running tests with Jest. They do not have an effect on the code in development or production.


    それだ!我々は現在、Jestとインポート機能を模擬する準備が整いました.

    ジェストMOCKインポート機能例コード
    依存性isInteger.js :
    // isInteger.js
    module.exports = (value) => Number.isSafeInteger(value);
    
    テストする単位isAtLeast18.js :
    // isAtLeast18.js
    const isInteger = require("./isInteger");
    
    module.exports = (value) => isInteger(value) && value >= 18;
    
    ユニットテストisAtLeast18.spec.js :
    // isAtLeast18.spec.js
    const isAtLeast18 = require("./isAtLeast18");
    const isInteger = require("./isInteger");
    
    // The mock factory returns a mocked function
    jest.mock("./isInteger", () => jest.fn());
    
    beforeEach(() => {
      isInteger.mockClear();
    });
    
    describe("isAtLeast18", () => {
      it("fails if value is not recognised as integer", () => {
        isInteger.mockImplementation(() => false);
    
        expect(isAtLeast18(123)).toBe(false);
        expect(isInteger).toHaveBeenCalledWith(123);
        expect(isInteger).toHaveBeenCalledTimes(1);
      });
    
      it("passes if value is recognised as integer and is at least 18", () => {
        isInteger.mockImplementation(() => true);
    
        expect(isAtLeast18(123)).toBe(true);
        expect(isInteger).toHaveBeenCalledWith(123);
        expect(isInteger).toHaveBeenCalledTimes(1);
      });
    });
    

    宿題と次のステップ
  • 追加のケースをカバーするために、より包括的なテストと使用備品を書いてください.あなたが前の記事からあなたの宿題をしたならば、あなたが去ったところから続けてみてください.
  • コードを修正するので、どんな失敗したテストでも、より新しい、より良い実装を書きます.
  • カバレッジレポートで100 %のコードカバレッジを達成する.
  • この記事を読む時間を割いてくれてありがとう!
    あなたは前にJestとインポート機能をモッキングしようとしたことがありますか?あなたの経験は何でしたか?
    コメントを残すとディスカッションを開始!