web-dev-qa-db-ja.com

Jestでグローバルをモックする

JestでnavigatorImage *などのグローバルオブジェクトをモックする方法はありますか?私はこれをほとんどあきらめ、一連のモック可能なユーティリティメソッドに任せました。例えば:

// Utils.js
export isOnline() {
    return navigator.onLine;
}

この小さな機能をテストするのは簡単ですが、凝っており、決定論的ではありません。私はそこに道の75%を得ることができますが、これは私が行くことができる限りです:

// Utils.test.js
it('knows if it is online', () => {
    const { isOnline } = require('path/to/Utils');

    expect(() => isOnline()).not.toThrow();
    expect(typeof isOnline()).toBe('boolean');
});

一方、このインダイレクションに問題がなければ、これらのユーティリティを介してnavigatorにアクセスできます。

// Foo.js
import { isOnline } from './Utils';

export default class Foo {
    doSomethingOnline() {
        if (!isOnline()) throw new Error('Not online');

        /* More implementation */            
    }
}

...そして確定的にこのようにテストします...

// Foo.test.js
it('throws when offline', () => {
    const Utils = require('../services/Utils');
    Utils.isOnline = jest.fn(() => isOnline);

    const Foo = require('../path/to/Foo').default;
    let foo = new Foo();

    // User is offline -- should fail
    let isOnline = false;
    expect(() => foo.doSomethingOnline()).toThrow();

    // User is online -- should be okay
    isOnline = true;
    expect(() => foo.doSomethingOnline()).not.toThrow();
});

私が使用したすべてのテストフレームワークの中で、Jestは最も完全なソリューションのように感じますが、テスト可能にするためだけに厄介なコードを書くと、テストツールが私を失望させているように感じます。

これが唯一の解決策ですか、それともRewireを追加する必要がありますか?

*にやにやしないでください。 Imageは、リモートネットワークリソースへのpingに最適です。

44
Andrew

すべてのテストで独自の環境が実行されるため、グローバルを上書きするだけでグローバルをモックできます。すべてのグローバル変数は、global名前空間によってアクセスできます。

global.navigator = {
  onLine: true
}

上書きは現在のテストでのみ効果があり、他のテストには影響しません。これは、Math.randomまたはDate.nowを処理する良い方法でもあります

Jsdomのいくつかの変更により、次のようにグローバルをモックする必要がある可能性があることに注意してください。

Object.defineProperty(globalObject, key, { value, writable: true });
65

受け入れられた回答が書かれてからJestが変更された可能性がありますが、Jestはテスト後にグローバルをリセットするようには見えません。添付のテストケースをご覧ください。

https://repl.it/repls/DecentPlushDeals

私が知る限り、これを回避する唯一の方法は、afterEach()またはafterAll()を使用してglobalへの割り当てをクリーンアップすることです。

8
smeltedcode

誰かがglobalstatic propertiesでモックする必要がある場合は、私の例が役立ちます:

beforeAll(() => {
    global.EventSource = jest.fn().mockImplementation(() => ({
      readyState: 0,
      close: jest.fn()
    }))

    global.EventSource.CONNECTING = 0
    global.EventSource.OPEN = 1
    global.EventSource.CLOSED = 2
  })
2