Jak zmienić próbną implementację na podstawie pojedynczego testu [Jestjs]

86

Chciałbym zmienić implementację mockowanej zależności na podstawie pojedynczego testu , rozszerzając domyślne zachowanie makiety i przywracając ją z powrotem do oryginalnej implementacji, gdy wykonywany jest następny test.

W skrócie to, co staram się osiągnąć:

  1. pozorowana zależność
  2. zmień / rozszerz próbną implementację w jednym teście
  3. powrócić do oryginalnego makiety po wykonaniu następnego testu

Obecnie używam Jest v21.

Oto jak wyglądałby typowy test Jest:

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

Oto, czego próbowałem do tej pory:


1 - mockFn.mockImplementationOnce (fn)

plusy

  • Przywraca pierwotną implementację po pierwszym wywołaniu

Cons

  • Zepsuje się, jeśli test wywoła bwiele razy
  • Nie powraca do oryginalnej implementacji, dopóki bnie zostanie wywołana (wyciek w następnym teście)

kod:

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 (nazwa modułu, fabryka, opcje)

plusy

  • Wyraźnie ponownie kpi z każdego testu

Cons

  • Nie można zdefiniować domyślnej implementacji próbnej dla wszystkich testów
  • Nie można rozszerzyć domyślnej implementacji wymuszającej ponowne zadeklarowanie każdej mockowanej metody

kod:

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 - Ręczne mockowanie metodami ustawiającymi (jak wyjaśniono tutaj )

plusy

  • Pełna kontrola nad fałszywymi wynikami

Cons

  • Dużo kodu standardowego
  • Trudne do utrzymania przez dłuższy czas

kod:

__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 (obiekt, nazwa metody)

Cons

  • Nie mogę powrócić mockImplementationdo pierwotnej wartości zwracanej, co ma wpływ na następne testy

kod:

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
źródło
Ładny. Ale jak zrobić opcję 2 dla modułu npm, takiego jak „@ private-repo / module”? Większość przykładów, które widzę, ma ścieżki względne? Czy to działa również w przypadku zainstalowanych modułów?
mrbinky3000

Odpowiedzi:

47

Przyjemnym wzorcem do pisania testu jest utworzenie funkcji ustawień fabrycznych, która zwraca dane potrzebne do przetestowania bieżącego modułu.

Poniżej znajduje się przykładowy kod następujący po drugim przykładzie, chociaż umożliwia dostarczanie wartości domyślnych i zastępowanie wartości w sposób wielokrotnego użytku.

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
źródło
40

Vanilla JS

Użyj 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 */ });
  // ...
});

Maszynopis

Aby zapobiec wyświetlaniu wiadomości mockImplementation nie jest właściwością funcToMock , będziesz musiał określić typ, np. Zmieniając górną linię z góry na następującą:

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

Pytanie dotyczące tego problemu można znaleźć tutaj: jest to makieta właściwości maszynopisu nie istnieje w typie

Słoik gliny
źródło
21

Trochę późno na imprezę, ale jeśli ktoś ma z tym problem.

Używamy TypeScript, ES6 i Babel do tworzenia natywnych rozwiązań.

Zwykle mockujemy zewnętrzne moduły NPM w __mocks__katalogu głównym .

Chciałem zastąpić określoną funkcję modułu w klasie Auth aws-amplify dla określonego testu.

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

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

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

Thomas Hagström
źródło