Wie kann ich einen ES6-Modulimport mit Jest verspotten?

281

Ich fange an zu denken, dass dies nicht möglich ist, aber ich möchte trotzdem fragen.

Ich möchte testen, ob eines meiner ES6-Module ein anderes ES6-Modul auf eine bestimmte Weise aufruft. Mit Jasmine ist das super einfach -

Der App-Code:

// myModule.js
import dependency from './dependency';

export default (x) => {
  dependency.doSomething(x * 2);
}

Und der Testcode:

//myModule-test.js
import myModule from '../myModule';
import dependency from '../dependency';

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    spyOn(dependency, 'doSomething');

    myModule(2);

    expect(dependency.doSomething).toHaveBeenCalledWith(4);
  });
});

Was ist das Äquivalent zu Jest? Ich denke, das ist so einfach zu tun, aber ich habe mir die Haare ausgerissen, um es herauszufinden.

Am nächsten komme ich, indem ich das imports durch requires ersetze und es in die Tests / Funktionen verschiebe. Beides möchte ich nicht tun.

// myModule.js
export default (x) => {
  const dependency = require('./dependency'); // yuck
  dependency.doSomething(x * 2);
}

//myModule-test.js
describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    jest.mock('../dependency');

    myModule(2);

    const dependency = require('../dependency'); // also yuck
    expect(dependency.doSomething).toBeCalledWith(4);
  });
});

Für Bonuspunkte würde ich gerne dafür sorgen, dass das Ganze funktioniert, wenn die darin enthaltene Funktion dependency.jsein Standardexport ist. Ich weiß jedoch, dass das Ausspionieren von Standardexporten in Jasmine nicht funktioniert (oder zumindest könnte ich es nie zum Laufen bringen), also hoffe ich nicht, dass es auch in Jest möglich ist.

Cam Jackson
quelle
Ich benutze Babel sowieso für dieses Projekt, daher macht es mir nichts aus, imports zu requires weiter zu transpilieren . Vielen Dank für die Hinweise.
Cam Jackson
Was ist, wenn ich ts Klasse A habe und eine Funktion
aufruft?
für diejenigen, die dieses Problem entdecken möchten mehr github.com/facebook/jest/issues/936
omeralper

Antworten:

219

Ich konnte dies mit einem Hack lösen import *. Es funktioniert sogar sowohl für benannte als auch für Standardexporte!

Für einen benannten Export:

// dependency.js
export const doSomething = (y) => console.log(y)

// myModule.js
import { doSomething } from './dependency';

export default (x) => {
  doSomething(x * 2);
}

// myModule-test.js
import myModule from '../myModule';
import * as dependency from '../dependency';

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    dependency.doSomething = jest.fn(); // Mutate the named export

    myModule(2);

    expect(dependency.doSomething).toBeCalledWith(4);
  });
});

Oder für einen Standardexport:

// dependency.js
export default (y) => console.log(y)

// myModule.js
import dependency from './dependency'; // Note lack of curlies

export default (x) => {
  dependency(x * 2);
}

// myModule-test.js
import myModule from '../myModule';
import * as dependency from '../dependency';

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    dependency.default = jest.fn(); // Mutate the default export

    myModule(2);

    expect(dependency.default).toBeCalledWith(4); // Assert against the default
  });
});

Wie Mihai Damian unten zu Recht hervorhob, mutiert dies das Modulobjekt von dependencyund wird daher auf andere Tests übertragen. Wenn Sie diesen Ansatz verwenden, sollten Sie den ursprünglichen Wert speichern und ihn nach jedem Test erneut zurücksetzen. Um dies mit Jest einfach zu machen, verwenden Sie die spyOn () -Methode anstelle der Methode jest.fn(), die die einfache Wiederherstellung des ursprünglichen Werts unterstützt, und vermeiden Sie daher das zuvor erwähnte "Lecken".

Cam Jackson
quelle
Danke für das Teilen. Ich denke, das Nettoergebnis
arcseldon
64
Dies funktioniert, ist aber wahrscheinlich keine gute Praxis. Änderungen an Objekten außerhalb des Testbereichs scheinen zwischen den Tests bestehen zu bleiben. Dies kann später zu unerwarteten Ergebnissen bei anderen Tests führen.
Mihai Damian
10
Anstatt jest.fn () zu verwenden, können Sie auch jest.spyOn () verwenden, um die ursprüngliche Methode später wiederherzustellen, damit sie nicht in andere Tests übergeht. Ich habe hier einen schönen Artikel über verschiedene Ansätze gefunden (jest.fn, jest.mock und jest.spyOn): medium.com/@rickhanlonii/understanding-jest-mocks-f0046c68e53c .
Martinsos
2
Nur ein Hinweis: Wenn dependencysich die Datei in derselben Datei befindet wie myModule, funktioniert sie nicht.
Lu Tran
2
Ich denke, dass dies mit Typescript nicht funktioniert. Das Objekt, das Sie mutieren, ist schreibgeschützt.
Adredx
171

Sie müssen das Modul verspotten und den Spion selbst einstellen:

import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency', () => ({
  doSomething: jest.fn()
}))

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    myModule(2);
    expect(dependency.doSomething).toBeCalledWith(4);
  });
});
Andreas Köberle
quelle
4
Das scheint nicht richtig zu sein. Ich verstehe: babel-plugin-jest-hoist: The second argument of jest.mock must be a function.Der Code wird also nicht einmal kompiliert.
Cam Jackson
3
Entschuldigung, ich habe meinen Code aktualisiert. Bitte beachten Sie auch, dass der Pfad in jest.mockrelativ zur Testdatei ist.
Andreas Köberle
1
Dies funktionierte bei mir jedoch nicht, wenn Standardexporte verwendet wurden.
Iris Schaffer
4
@IrisSchaffer Damit dies mit dem Standardexport funktioniert, den Sie dem Scheinobjekt hinzufügen müssen __esModule: true. Dies ist das interne Flag, das vom transpilierten Code verwendet wird, um zu bestimmen, ob es sich um ein transpiliertes es6-Modul oder ein commonjs-Modul handelt.
Johannes Lumpe
24
Verspottete Standardexporte: jest.mock('../dependency', () => ({ default: jest.fn() }))
Neob91
50

So verspotten Sie den Standardexport eines ES6-Abhängigkeitsmoduls mithilfe von Scherz:

import myModule from '../myModule';
import dependency from '../dependency';

jest.mock('../dependency');

// If necessary, you can place a mock implementation like this:
dependency.mockImplementation(() => 42);

describe('myModule', () => {
  it('calls the dependency once with double the input', () => {
    myModule(2);

    expect(dependency).toHaveBeenCalledTimes(1);
    expect(dependency).toHaveBeenCalledWith(4);
  });
});

Die anderen Optionen haben in meinem Fall nicht funktioniert.

Falsarella
quelle
6
Was ist der beste Weg, dies zu bereinigen, wenn ich nur einen Test machen möchte? drinnen nach jedem? `` `` afterEach (() => {jest.unmock (../ dependency ');}) `` ``
nxmohamad
1
@falsarella funktioniert doMock in diesem Fall tatsächlich? Ich habe ein sehr ähnliches Problem und es tut nichts, wenn ich versuche, jest.doMock in einem bestimmten Test zu scherzen, in dem jest.mock für das gesamte Modul korrekt funktioniert
Progress1ve
1
@ Progress1ve können Sie versuchen, jest.mock mit mockImplementationOnce auch zu verwenden
falsarella
1
Ja, das ist ein gültiger Vorschlag, aber dafür muss der Test der erste sein, und ich bin kein Fan davon, Tests so zu schreiben. Ich habe diese Probleme umgangen, indem ich ein externes Modul importiert und spyOn für bestimmte Funktionen verwendet habe.
Progress1ve
1
@ Progress1ve hmm Ich wollte das mockImplementationOnce in jeden spezifischen Test einfügen ... trotzdem bin ich froh, dass Sie eine Lösung gefunden haben :)
Falsarella
38

Hinzufügen zu mehr Antwort von Andreas. Ich hatte das gleiche Problem mit ES6-Code, wollte aber die Importe nicht mutieren. Das sah hackig aus. Also habe ich das gemacht

import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency');

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    myModule(2);
  });
});

Und dependency.js im Ordner "__ mocks __" parallel zu dependency.js hinzugefügt . Das hat bei mir funktioniert. Dies gab mir auch die Möglichkeit, geeignete Daten aus der Scheinimplementierung zurückzugeben. Stellen Sie sicher, dass Sie den richtigen Pfad zu dem Modul angeben, das Sie verspotten möchten.

mdsAyubi
quelle
Danke dafür. Ich werde es versuchen. Gefiel diese Lösung auch - stackoverflow.com/a/38414160/1882064
arcseldon
Was mir an diesem Ansatz gefällt, ist, dass Sie die Möglichkeit haben, ein manuelles Modell für alle Gelegenheiten bereitzustellen, in denen Sie ein bestimmtes Modul verspotten möchten. Ich habe zum Beispiel einen Übersetzungshelfer, der an vielen Orten verwendet wird. Die __mocks__/translations.jsDatei exportiert einfach standardmäßig jest.fn()in etwa:export default jest.fn((id) => id)
Iris Schaffer
Sie können auch jest.genMockFromModuleMocks aus Modulen generieren. facebook.github.io/jest/docs/…
Varunkumar Nagarajan
2
Zu beachten ist, dass bei ES6-Modulen, über export default jest.genMockFromModule('../dependency')die verspottet wird, alle Funktionen dependency.defaultnach dem Aufruf von `jest.mock ('.. dependency') zugewiesen werden , sich aber ansonsten wie erwartet verhalten.
JHK
7
Wie sieht Ihre Testaussage aus? Das scheint ein wichtiger Teil der Antwort zu sein. expect(???)
Stein
14

Beim schnellen Vorlauf bis 2020 fand ich diesen Link als Lösung. Verwenden nur der ES6-Modulsyntax https://remarkablemark.org/blog/2018/06/28/jest-mock-default-named-export/

// esModule.js
export default 'defaultExport';
export const namedExport = () => {};

// esModule.test.js
jest.mock('./esModule', () => ({
  __esModule: true, // this property makes it work
  default: 'mockedDefaultExport',
  namedExport: jest.fn(),
}));

import defaultExport, { namedExport } from './esModule';
defaultExport; // 'mockedDefaultExport'
namedExport; // mock function

Außerdem müssen Sie wissen (was ich eine Weile gebraucht habe, um herauszufinden), dass Sie im Test nicht jest.mock () aufrufen können. Sie müssen es auf der obersten Ebene des Moduls aufrufen. Sie können jedoch mockImplementation () in einzelnen Tests aufrufen, wenn Sie verschiedene Mocks für verschiedene Tests einrichten möchten.

Andy
quelle
5

Die Frage ist bereits beantwortet, aber Sie können sie folgendermaßen lösen:

dependency.js

const doSomething = (x) => x
export default doSomething;

myModule.js:

import doSomething from "./dependency";

export default (x) => doSomething(x * 2);

myModule.spec.js:

jest.mock('../dependency');
import doSomething from "../dependency";
import myModule from "../myModule";

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    doSomething.mockImplementation((x) => x * 10)

    myModule(2);

    expect(doSomething).toHaveBeenCalledWith(4);
    console.log(myModule(2)) // 40
  });
});
Schlank
quelle
Aber "erfordern" ist die CommonJS-Syntax - OP fragte nach ES6-Modulen
Andy
@ Andy danke für deinen Kommentar, ich habe meine Antwort aktualisiert. Übrigens das Gleiche in der Logik.
Slim
2

Ich habe das anders gelöst. Angenommen, Sie haben Ihre dependency.js

export const myFunction = () => { }

Ich erstelle außerdem eine depdency.mock.js-Datei mit folgendem Inhalt:

export const mockFunction = jest.fn();

jest.mock('dependency.js', () => ({ myFunction: mockFunction }));

und im Test, bevor ich die Datei importiere, die die von mir verwendete Abhängigkeit hat:

import { mockFunction } from 'dependency.mock'
import functionThatCallsDep from './tested-code'

it('my test', () => {
    mockFunction.returnValue(false);

    functionThatCallsDep();

    expect(mockFunction).toHaveBeenCalled();

})
Felipe Leusin
quelle