So ändern Sie die Scheinimplementierung pro Test [Jestjs]

86

Ich möchte die Implementierung einer verspotteten Abhängigkeit pro Test ändern, indem ich das Verhalten des Standard-Mocks erweitere und es bei der Ausführung des nächsten Tests auf die ursprüngliche Implementierung zurücksetze.

Kurz gesagt, das versuche ich zu erreichen:

  1. Mock Abhängigkeit
  2. Scheinimplementierung in einem einzigen Test ändern / erweitern
  3. Wenn der nächste Test ausgeführt wird, kehren Sie zum ursprünglichen Modell zurück

Ich benutze gerade Jest v21.

So würde ein typischer Scherztest aussehen:

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

Folgendes habe ich bisher versucht:


1 - mockFn.mockImplementationOnce (fn)

Profis

  • Kehrt nach dem ersten Aufruf zur ursprünglichen Implementierung zurück

Nachteile

  • Es wird unterbrochen, wenn der Test bmehrmals aufgerufen wird
  • Die ursprüngliche Implementierung wird erst wiederhergestellt, wenn sie bnicht aufgerufen wird (Leck im nächsten Test).

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 (Modulname, Fabrik, Optionen)

Profis

  • Verspottet bei jedem Test explizit neu

Nachteile

  • Die Standard-Mock-Implementierung kann nicht für alle Tests definiert werden
  • Das Erzwingen der Standardimplementierung kann nicht erweitert werden, um jede verspottete Methode erneut zu deklarieren

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 - Manuelles Verspotten mit Setter-Methoden (wie hier erklärt )

Profis

  • Volle Kontrolle über verspottete Ergebnisse

Nachteile

  • Viel Boilerplate-Code
  • Langfristig schwer zu pflegen

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 (Objekt, Methodenname)

Nachteile

  • Ich kann nicht mockImplementationzum ursprünglichen verspotteten Rückgabewert zurückkehren, was sich auf die nächsten Tests auswirkt

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
quelle
Nett. Aber wie macht man Option 2 für ein npm-Modul wie '@ private-repo / module'? Die meisten Beispiele, die ich sehe, haben relative Pfade? Funktioniert dies auch für installierte Module?
mrbinky3000

Antworten:

47

Ein schönes Muster zum Schreiben von Tests ist das Erstellen einer Setup-Factory-Funktion, die die Daten zurückgibt, die Sie zum Testen des aktuellen Moduls benötigen.

Im Folgenden finden Sie einen Beispielcode, der Ihrem zweiten Beispiel folgt, obwohl die Bereitstellung von Standard- und Überschreibungswerten auf wiederverwendbare Weise möglich ist.

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
quelle
40

Vanilla JS

Verwenden Sie 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 */ });
  // ...
});

Typoskript

Um zu verhindern, dass die Nachricht mockImplementation keine Eigenschaft von funcToMock ist , müssen Sie den Typ angeben, z. B. indem Sie die oberste Zeile von oben in die folgende ändern:

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

Eine Frage zu diesem Problem finden Sie hier: jest typescript property mock existiert für type nicht

Ein Glas Ton
quelle
21

Wenig spät zur Party, aber wenn jemand anderes Probleme damit hat.

Wir verwenden TypeScript, ES6 und babel für die reaktionsnative Entwicklung.

Normalerweise verspotten wir externe NPM-Module im Stammverzeichnis __mocks__.

Ich wollte eine bestimmte Funktion eines Moduls in der Auth-Klasse von aws-amplify für einen bestimmten Test überschreiben.

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

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

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

Thomas Hagström
quelle