207 votes

Comment changer l'implémentation simulée sur une base de test individuel ?

Je voudrais modifier la mise en œuvre d'une dépendance moquée sur une base de test unique en étendant le comportement par défaut du mock et en le ramenant à la mise en œuvre d'origine lorsque le test suivant s'exécute.

Plus brièvement, voici ce que j'essaie d'accomplir :

  1. Mocker la dépendance
  2. Modifier/étendre la mise en œuvre du mock dans un seul test
  3. Revenir à la mise en œuvre initiale du mock lorsque le test suivant s'exécute

Je utilise actuellement Jest v21. Voici à quoi ressemblerait un test 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('devrait tester avec le mock par défaut', () => {
    myMockedModule.a(); // === true
    myMockedModule.b(); // === true
  });

  it('devrait remplacer le résultat du mock myMockedModule.b (et laisser les autres méthodes intactes)', () => {
    // Étendre le changement du mock
    myMockedModule.a(); // === true
    myMockedModule.b(); // === 'surchargé'
    // Restaurer le mock à la mise en œuvre d'origine sans effets secondaires
  });

  it('devrait revenir au mock par défaut de myMockedModule', () => {
    myMockedModule.a(); // === true
    myMockedModule.b(); // === true
  });
});

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

  1. mockFn.mockImplementationOnce(fn)

    it('devrait remplacer le résultat du mock myModule.b (et laisser les autres méthodes intactes)', () => {
    
      myMockedModule.b.mockImplementationOnce(() => 'surchargé');
    
      myModule.a(); // === true
      myModule.b(); // === 'surchargé'
    });

    Avantages

    • Reviens à la mise en œuvre d'origine après le premier appel

    Inconvénients

    • Il casse si le test appelle b plusieurs fois
    • Il ne revient pas à la mise en œuvre originale tant que b n'est pas appelé (fuite dans le test suivant)
  2. jest.doMock(moduleName, factory, options)

    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'
    });

    Avantages

    • Re-mocks explicitement à chaque test

    Inconvénients

    • Impossible de définir une mise en œuvre par défaut pour tous les tests
    • Impossible d'étendre l'implémentation par défaut obligeant à redéclarer chaque méthode moquée
  3. Mocking manuel avec des méthodes setter (comme expliqué ici)

    // __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('devrait remplacer le résultat du mock myModule.b (et laisser les autres méthodes intactes)', () => {
      myModule.__setB('surchargé');
    
      myModule.a(); // === true
      myModule.b(); // === 'surchargé'
    
      myModule.__reset();
    });

    Avantages

    • Contrôle total sur les résultats moqués

    Inconvénients

    • Beaucoup de code redondant
    • Difficile à maintenir à long terme
  4. jest.spyOn(object, methodName)

    beforeEach(() => {
      jest.clearAllMocks();
      jest.restoreAllMocks();
    });
    
    // Mock myModule
    jest.mock('../myModule');
    
    it('devrait remplacer le résultat du mock myModule.b (et laisser les autres méthodes intactes)', () => {
    
      const spy = jest.spyOn(myMockedModule, 'b').mockImplementation(() => 'surchargé');
    
      myMockedModule.a(); // === true
      myMockedModule.b(); // === 'surchargé'
    
      // Comment revenir à la valeur moquée d'origine ?
    });

    Inconvénients

    • Je ne peux pas revenir à mockImplementation à la valeur de retour moquée d'origine, affectant donc les tests suivants

1 votes

Bien. Mais comment faites-vous l'option 2 pour un module npm comme '@private-repo/module'? La plupart des exemples que je vois ont des chemins relatifs? Est-ce que cela fonctionne aussi pour les modules installés?

0voto

manna Points 81

C'est une façon très cool que j'ai découverte sur ce blog https://mikeborozdin.com/post/changing-jest-mocks-between-tests/

import { sayHello } from './say-hello';
import * as config from './config';

jest.mock('./config', () => ({
  __esModule: true,
  CAPITALIZE: null
}));

describe('say-hello', () => {
  test('Capitalizes name if config requires that', () => {
    config.CAPITALIZE = true;

    expect(sayHello('john')).toBe('Salut, John');
  });

  test('does not capitalize name if config does not require that', () => {
    config.CAPITALIZE = false;

    expect(sayHello('john')).toBe('Salut, john');
  });
});

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X