Comment modifier l'implémentation fictive sur une base de test unique [Jestjs]

86

Je voudrais changer l'implémentation d'une dépendance simulée sur une base de test unique en étendant le comportement de la maquette par défaut et en le rétablissant à l'implémentation d'origine lorsque le prochain test s'exécute.

Plus brièvement, voici ce que j'essaie de réaliser:

  1. dépendance simulée
  2. modifier / étendre l'implémentation fictive en un seul test
  3. revenir à la maquette d'origine lors de l'exécution du prochain test

J'utilise actuellement Jest v21.

Voici à quoi ressemblerait un test Jest typique:

__mocks__/myModule.js

const myMockedModule = jest.genMockFromModule('../myModule');

myMockedModule.a = jest.fn(() => true);
myMockedModule.b = jest.fn(() => true);

export default myMockedModule;

__tests__/myTest.js

import myMockedModule from '../myModule';

// Mock myModule
jest.mock('../myModule');

beforeEach(() => {
  jest.clearAllMocks();
});

describe('MyTest', () => {
  it('should test with default mock', () => {
    myMockedModule.a(); // === true
    myMockedModule.b(); // === true
  });

  it('should override myMockedModule.b mock result (and leave the other methods untouched)', () => {
    // Extend change mock
    myMockedModule.a(); // === true
    myMockedModule.b(); // === 'overridden'
    // Restore mock to original implementation with no side effects
  });

  it('should revert back to default myMockedModule mock', () => {
    myMockedModule.a(); // === true
    myMockedModule.b(); // === true
  });
});

Voici ce que j'ai essayé jusqu'à présent:


1 - mockFn.mockImplementationOnce (fn)

avantages

  • Revenir à l'implémentation d'origine après le premier appel

les inconvénients

  • Il casse si le test appelle bplusieurs fois
  • Il ne revient pas à l'implémentation d'origine tant qu'il bn'est pas appelé (fuite lors du test suivant)

code:

it('should override myModule.b mock result (and leave the other methods untouched)', () => {

  myMockedModule.b.mockImplementationOnce(() => 'overridden');

  myModule.a(); // === true
  myModule.b(); // === 'overridden'
});

2 - jest.doMock (nom du module, usine, options)

avantages

  • Se moque explicitement à chaque test

les inconvénients

  • Impossible de définir l'implémentation fictive par défaut pour tous les tests
  • Impossible d'étendre l'implémentation par défaut forçant à re-déclarer chaque méthode simulée

code:

it('should override myModule.b mock result (and leave the other methods untouched)', () => {

  jest.doMock('../myModule', () => {
    return {
      a: jest.fn(() => true,
      b: jest.fn(() => 'overridden',
    }
  });

  myModule.a(); // === true
  myModule.b(); // === 'overridden'
});

3 - Mocking manuel avec des méthodes de setter (comme expliqué ici )

avantages

  • Contrôle total des résultats simulés

les inconvénients

  • Lot de code passe-partout
  • Difficile à maintenir à long terme

code:

__mocks__/myModule.js

const myMockedModule = jest.genMockFromModule('../myModule');

let a = true;
let b = true;

myMockedModule.a = jest.fn(() => a);
myMockedModule.b = jest.fn(() => b);

myMockedModule.__setA = (value) => { a = value };
myMockedModule.__setB = (value) => { b = value };
myMockedModule.__reset = () => {
  a = true;
  b = true;
};
export default myMockedModule;

__tests__/myTest.js

it('should override myModule.b mock result (and leave the other methods untouched)', () => {
  myModule.__setB('overridden');

  myModule.a(); // === true
  myModule.b(); // === 'overridden'

  myModule.__reset();
});

4 - jest.spyOn (objet, methodName)

les inconvénients

  • Je ne peux pas revenir mockImplementationà la valeur de retour simulée d'origine, ce qui affecte les tests suivants

code:

beforeEach(() => {
  jest.clearAllMocks();
  jest.restoreAllMocks();
});

// Mock myModule
jest.mock('../myModule');

it('should override myModule.b mock result (and leave the other methods untouched)', () => {

  const spy = jest.spyOn(myMockedModule, 'b').mockImplementation(() => 'overridden');

  myMockedModule.a(); // === true
  myMockedModule.b(); // === 'overridden'

  // How to get back to original mocked value?
});
Andrea Carraro
la source
Agréable. Mais comment faire l'option 2 pour un module npm comme '@ private-repo / module'? La plupart des exemples que je vois ont des chemins relatifs? Cela fonctionne-t-il également pour les modules installés?
mrbinky3000

Réponses:

47

Un bon modèle pour le test d'écriture est de créer une fonction de fabrique de configuration qui renvoie les données dont vous avez besoin pour tester le module actuel.

Vous trouverez ci-dessous un exemple de code suivant votre deuxième exemple, bien qu'il autorise la fourniture de valeurs par défaut et de remplacement de manière réutilisable.

const spyReturns = returnValue => jest.fn(() => returnValue);

describe("scenario", () => {
  const setup = (mockOverrides) => {
    const mockedFunctions =  {
      a: spyReturns(true),
      b: spyReturns(true),
      ...mockOverrides
    }
    return {
      mockedModule: jest.doMock('../myModule', () => mockedFunctions)
    }
  }

  it("should return true for module a", () => {
    const { mockedModule } = setup();
    expect(mockedModule.a()).toEqual(true)
  });

  it("should return override for module a", () => {
    const EXPECTED_VALUE = "override"
    const { mockedModule } = setup({ a: spyReturns(EXPECTED_VALUE)});
    expect(mockedModule.a()).toEqual(EXPECTED_VALUE)
  });
});
user1095118
la source
40

Vanille JS

Utilisez mockFn.mockImplementation (fn) .

import { funcToMock } from './somewhere';
jest.mock('./somewhere');

beforeEach(() => {
  funcToMock.mockImplementation(() => { /* default implementation */ });
});

test('case that needs a different implementation of funcToMock', () => {
  funcToMock.mockImplementation(() => { /* implementation specific to this test */ });
  // ...
});

Manuscrit

Pour éviter que le message mockImplementation ne soit une propriété de funcToMock , vous devrez spécifier le type, par exemple en changeant la ligne supérieure de ci-dessus à la suivante:

import { (funcToMock as jest.Mock) } from './somewhere';

Une question traitant de ce problème peut être trouvée ici: la propriété jest dactylographiée mock n'existe pas sur le type

Un pot d'argile
la source
21

Un peu tard à la fête, mais si quelqu'un d'autre a des problèmes avec ça.

Nous utilisons TypeScript, ES6 et babel pour le développement natif de réaction.

Nous nous moquons généralement des modules NPM externes dans le __mocks__répertoire racine .

Je voulais remplacer une fonction spécifique d'un module dans la classe Auth de aws-amplify pour un test spécifique.

    import { Auth } from 'aws-amplify';
    import GetJwtToken from './GetJwtToken';
    ...
    it('When idToken should return "123"', async () => {
      const spy = jest.spyOn(Auth, 'currentSession').mockImplementation(() => ({
        getIdToken: () => ({
          getJwtToken: () => '123',
        }),
      }));

      const result = await GetJwtToken();
      expect(result).toBe('123');
      spy.mockRestore();
    });

Gist: https://gist.github.com/thomashagstrom/e5bffe6c3e3acec592201b6892226af2

Tutoriel: https://medium.com/p/b4ac52a005d#19c5

Thomas Hagström
la source