Mam następujący moduł, który próbuję przetestować w Jest:
// myModule.js
export function otherFn() {
console.log('do something');
}
export function testFn() {
otherFn();
// do other things
}
Jak pokazano powyżej, eksportuje niektóre nazwane funkcje i, co ważne, testFn
używa otherFn
.
W Jest, kiedy piszę mój test jednostkowy dla testFn
, chcę mockować otherFn
funkcję, ponieważ nie chcę, aby błędy otherFn
wpływały na mój test jednostkowy testFn
. Mój problem polega na tym, że nie jestem pewien, jak najlepiej to zrobić:
// myModule.test.js
jest.unmock('myModule');
import { testFn, otherFn } from 'myModule';
describe('test category', () => {
it('tests something about testFn', () => {
// I want to mock "otherFn" here but can't reassign
// a.k.a. can't do otherFn = jest.fn()
});
});
Każda pomoc / wgląd jest mile widziany.
unit-testing
ecmascript-6
jestjs
Jon Rubins
źródło
źródło
otherFn
do oddzielnego modułu i udawać.function A
kto dzwoni,function B
ale nie chcę wykonywać prawdziwej implementacji,function B
ponieważ chcę tylko przetestować logikę zaimplementowaną wfunction A
@testing-library/react
każdemu, kto tam jest), ale wiem, że jest to kontrowersyjny temat.Odpowiedzi:
Użyj
jest.requireActual()
wewnątrzjest.mock()
Przykład
Wolę to zwięzłe użycie, gdy potrzebujesz i rozpowszechniasz w zwracanym obiekcie:
// myModule.test.js jest.mock('./myModule.js', () => ( { ...(jest.requireActual('./myModule.js')), otherFn: jest.fn() } )) import { otherFn } from './myModule.js' describe('test category', () => { it('tests something about otherFn', () => { otherFn.mockReturnValue('foo') expect(otherFn()).toBe('foo') }) })
Ta metoda jest również opisana w dokumentacji Jest's Manual Mocks (pod koniec przykładów ):
źródło
return
instrukcję i umieszczając treść funkcji strzałki w nawiasach: np.jest.mock('./myModule', () => ({ ...jest.requireActual('./myModule'), otherFn: () => {}}))
...jest.requireActual
nie działało poprawnie, ponieważ mam aliasowanie ścieżki za pomocą babel .. Działa z...require.requireActual
lub po usunięciu aliasingu ze ścieżkiotherFun
zostało wywołane w tym przypadku? ZakładającotherFn: jest.fn()
mockReturnValue
metodę, aby lepiej zademonstrować, że zamiast oryginału jest wywoływana wersja symulowana, ale jeśli naprawdę chcesz tylko sprawdzić, czy została wywołana bez potwierdzania wartości zwracanej, możesz użyć dopasowania jest.toHaveBeenCalled()
.import m from '../myModule';
U mnie nie działa, użyłem:
import * as m from '../myModule'; m.otherFn = jest.fn();
źródło
clearkMocks: true
jest package.json config. facebook.github.io/jest/docs/en/mock-function-api.htmlWygląda na to, że spóźniłem się na to przyjęcie, ale tak, jest to możliwe.
testFn
wystarczy zadzwonićotherFn
za pomocą modułu .Jeśli
testFn
używa modułu do wywołania,otherFn
wówczas eksport modułu dlaotherFn
może być mockowany itestFn
wywoła model.Oto działający przykład:
myModule.js
import * as myModule from './myModule'; // import myModule into itself export function otherFn() { return 'original value'; } export function testFn() { const result = myModule.otherFn(); // call otherFn using the module // do other things return result; }
myModule.test.js
import * as myModule from './myModule'; describe('test category', () => { it('tests something about testFn', () => { const mock = jest.spyOn(myModule, 'otherFn'); // spy on otherFn mock.mockReturnValue('mocked value'); // mock the return value expect(myModule.testFn()).toBe('mocked value'); // SUCCESS mock.mockRestore(); // restore otherFn }); });
źródło
exports.otherFn()
exports
nie istnieje w ES6. Wywołanieexports.otherFn()
działa teraz, ponieważ ES6 jest kompilowane do wcześniejszej składni modułu, ale zepsuje się, gdy ES6 będzie obsługiwane natywnie.Transpiled kod nie pozwoli Babel na pobranie powiązania, do którego
otherFn()
się odnosi. Jeśli używasz wyrażenia funkcji, powinieneś być w stanie uzyskać mockowanieotherFn()
.// myModule.js exports.otherFn = () => { console.log('do something'); } exports.testFn = () => { exports.otherFn(); // do other things }
// myModule.test.js import m from '../myModule'; m.otherFn = jest.fn();
Ale jak @kentcdodds wspomniał w poprzednim komentarzu, prawdopodobnie nie chciałbyś kpić
otherFn()
. Zamiast tego po prostu napisz nową specyfikacjęotherFn()
i udawaj wszelkie niezbędne wywołania, które wykonuje.Na przykład, jeśli
otherFn()
wysyła żądanie http ...// myModule.js exports.otherFn = () => { http.get('http://some-api.com', (res) => { // handle stuff }); };
Tutaj chciałbyś kpić
http.get
i aktualizować swoje potwierdzenia na podstawie twoich udawanych implementacji.// myModule.test.js jest.mock('http', () => ({ get: jest.fn(() => { console.log('test'); }), }));
źródło
otherFn
jest uszkodzony, nie przejdzie wszystkich testów, które zależą od tego. Również jeśliotherFn
masz 5 ifów w środku, być może będziesz musiał sprawdzić, czy twójtestFn
działa dobrze dla wszystkich tych przypadków podrzędnych. Będziesz mieć teraz o wiele więcej ścieżek kodu do przetestowania.Wiem, że zadawano to dawno temu, ale właśnie natknąłem się na tę właśnie sytuację i w końcu znalazłem rozwiązanie, które zadziała. Więc pomyślałem, że podzielę się tutaj.
Dla modułu:
// myModule.js export function otherFn() { console.log('do something'); } export function testFn() { otherFn(); // do other things }
Możesz zmienić na następujące:
// myModule.js export const otherFn = () => { console.log('do something'); } export const testFn = () => { otherFn(); // do other things }
eksportowanie ich jako stałych zamiast funkcji. Uważam, że problem ma związek z podnoszeniem JavaScript i używanie
const
zapobiega temu zachowaniu.Następnie w swoim teście możesz mieć coś takiego:
import * as myModule from 'myModule'; describe('...', () => { jest.spyOn(myModule, 'otherFn').mockReturnValue('what ever you want to return'); // or myModule.otherFn = jest.fn(() => { // your mock implementation }); });
Twoje makiety powinny teraz działać tak, jak zwykle byś oczekiwał.
źródło
Rozwiązałem swój problem za pomocą kombinacji odpowiedzi, które znalazłem tutaj:
myModule.js
import * as myModule from './myModule'; // import myModule into itself export function otherFn() { return 'original value'; } export function testFn() { const result = myModule.otherFn(); // call otherFn using the module // do other things return result; }
myModule.test.js
import * as myModule from './myModule'; describe('test category', () => { let otherFnOrig; beforeAll(() => { otherFnOrig = myModule.otherFn; myModule.otherFn = jest.fn(); }); afterAll(() => { myModule.otherFn = otherFnOrig; }); it('tests something about testFn', () => { // using mock to make the tests }); });
źródło
Oprócz pierwszej odpowiedzi tutaj, możesz użyć babel-plugin-rewire do mockowania zaimportowanej nazwanej funkcji. Możesz przejrzeć tę sekcję powierzchownie, aby uzyskać informacje o ponownym okablowaniu funkcji .
Jedną z bezpośrednich korzyści dla twojej sytuacji jest to, że nie musisz zmieniać sposobu wywoływania drugiej funkcji ze swojej funkcji.
źródło
Opierając się na odpowiedzi Briana Adamsa, w ten sposób mogłem zastosować to samo podejście w TypeScript. Co więcej, za pomocą jest.doMock () można mockować funkcje modułu tylko w niektórych specyficznych testach pliku testowego i dostarczać dla każdego z nich indywidualne mockowe implementacje.
src / module.ts
import * as module from './module'; function foo(): string { return `foo${module.bar()}`; } function bar(): string { return 'bar'; } export { foo, bar };
test / module.test.ts
import { mockModulePartially } from './helpers'; import * as module from '../src/module'; const { foo } = module; describe('test suite', () => { beforeEach(function() { jest.resetModules(); }); it('do not mock bar 1', async() => { expect(foo()).toEqual('foobar'); }); it('mock bar', async() => { mockModulePartially('../src/module', () => ({ bar: jest.fn().mockImplementation(() => 'BAR') })); const module = await import('../src/module'); const { foo } = module; expect(foo()).toEqual('fooBAR'); }); it('do not mock bar 2', async() => { expect(foo()).toEqual('foobar'); }); });
test / helpers.ts
export function mockModulePartially( modulePath: string, mocksCreator: (originalModule: any) => Record<string, any> ): void { const testRelativePath = path.relative(path.dirname(expect.getState().testPath), __dirname); const fixedModulePath = path.relative(testRelativePath, modulePath); jest.doMock(fixedModulePath, () => { const originalModule = jest.requireActual(fixedModulePath); return { ...originalModule, ...mocksCreator(originalModule) }; }); }
Mockowanie funkcji modułu jest przenoszone do funkcji pomocniczej
mockModulePartially
znajdującej się w oddzielnym pliku, dzięki czemu może być używana z różnych plików testowych (które zwykle mogą znajdować się w innych katalogach). Polega naexpect.getState().testPath
naprawieniu ścieżki do modułu (modulePath
), który jest mockowany (uczynienie go względnym dohelpers.ts
zawierającegomockModulePartially
).mocksCreator
funkcja przekazana jako drugi argumentmockModulePartially
powinna zwrócić makiety modułu. Ta funkcjaoriginalModule
może opcjonalnie na niej polegać.źródło