web-dev-qa-db-fra.com

Malsains globals dans Jest

Est-il possible dans Jest de se moquer d'objets globaux, tels que navigator ou Image *? J'ai à peu près abandonné cela et laissé le soin à une série de méthodes utilitaires moquables. Par exemple:

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

Tester cette fonction minuscule est simple, mais cruel et pas du tout déterministe. Je peux obtenir 75% du chemin, mais c'est aussi loin que je peux aller:

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

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

D'autre part, si je suis d'accord avec cette indirection, je peux maintenant accéder à navigator via ces utilitaires:

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

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

        /* More implementation */            
    }
}

... et tester de manière déterministe comme ça ...

// 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();
});

Parmi tous les frameworks de test que j'ai utilisés, Jest semble être la solution la plus complète, mais chaque fois que j'écris un code compliqué juste pour le rendre testable, j'ai l'impression que mes outils de test me laissent tomber.

Est-ce la seule solution ou dois-je ajouter Rewire?

* Ne souriez pas. Image est fantastique pour envoyer une requête ping à une ressource réseau distante.

44
Andrew

Comme chaque test exécute son propre environnement, vous pouvez vous moquer des globaux en les écrasant. Tous les vars globaux sont accessibles via l’espace de noms global.

global.navigator = {
  onLine: true
}

L'écrasement n'a que des effets sur votre test actuel et n'affectera pas les autres. C'est aussi un bon moyen de gérer Math.random ou Date.now

Notez que grâce à certains changements dans jsdom, il est possible que vous deviez vous moquer de globals comme ceci:

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

Jest peut avoir changé depuis que la réponse acceptée a été écrite, mais Jest ne semble pas réinitialiser votre réponse globale après le test. Veuillez consulter les tests ci-joints.

https://repl.it/repls/DecentPlushDeals

Autant que je sache, le seul moyen de contourner ce problème consiste à utiliser afterEach() ou afterAll() pour nettoyer vos affectations dans global.

8
smeltedcode

Si quelqu'un a besoin de se moquer de global avec propriétés statiques, alors mon exemple pourrait aider:

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

    global.EventSource.CONNECTING = 0
    global.EventSource.OPEN = 1
    global.EventSource.CLOSED = 2
  })
2
Kaspar Püüding