Ich habe die folgenden ES6-Module:
network.js
export function getDataFromServer() {
return ...
}
widget.js
import { getDataFromServer } from 'network.js';
export class Widget() {
constructor() {
getDataFromServer("dataForWidget")
.then(data => this.render(data));
}
render() {
...
}
}
Ich suche nach einer Möglichkeit, Widget mit einer Scheininstanz von zu testen getDataFromServer
. Wenn ich <script>
wie in Karma separate s anstelle von ES6-Modulen verwenden würde, könnte ich meinen Test wie folgt schreiben:
describe("widget", function() {
it("should do stuff", function() {
let getDataFromServer = spyOn(window, "getDataFromServer").andReturn("mockData")
let widget = new Widget();
expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget");
expect(otherStuff).toHaveHappened();
});
});
Wenn ich jedoch ES6-Module einzeln außerhalb eines Browsers teste (wie bei Mocha + babel), würde ich Folgendes schreiben:
import { Widget } from 'widget.js';
describe("widget", function() {
it("should do stuff", function() {
let getDataFromServer = spyOn(?????) // How to mock?
.andReturn("mockData")
let widget = new Widget();
expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget");
expect(otherStuff).toHaveHappened();
});
});
Okay, aber jetzt getDataFromServer
ist es nicht verfügbar window
(nun, es gibt überhaupt keine window
), und ich kenne keine Möglichkeit, Dinge direkt in widget.js
den eigenen Bereich zu injizieren .
Wohin gehe ich von hier aus?
- Gibt es eine Möglichkeit, auf den Umfang von zuzugreifen
widget.js
oder zumindest seine Importe durch meinen eigenen Code zu ersetzen? - Wenn nicht, wie kann ich
Widget
testbar machen ?
Sachen, über die ich nachgedacht habe:
ein. Manuelle Abhängigkeitsinjektion.
Entfernen Sie alle Importe von widget.js
und erwarten Sie, dass der Anrufer die Deps bereitstellt.
export class Widget() {
constructor(deps) {
deps.getDataFromServer("dataForWidget")
.then(data => this.render(data));
}
}
Es ist mir sehr unangenehm, die öffentliche Oberfläche von Widget so durcheinander zu bringen und Implementierungsdetails offenzulegen. No Go.
b. Legen Sie die Importe offen, um sie verspotten zu können.
Etwas wie:
import { getDataFromServer } from 'network.js';
export let deps = {
getDataFromServer
};
export class Widget() {
constructor() {
deps.getDataFromServer("dataForWidget")
.then(data => this.render(data));
}
}
dann:
import { Widget, deps } from 'widget.js';
describe("widget", function() {
it("should do stuff", function() {
let getDataFromServer = spyOn(deps.getDataFromServer) // !
.andReturn("mockData");
let widget = new Widget();
expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget");
expect(otherStuff).toHaveHappened();
});
});
Dies ist weniger invasiv, erfordert jedoch, dass ich für jedes Modul eine Menge Boilerplate schreibe, und es besteht immer noch das Risiko, dass ich es getDataFromServer
anstelle von deps.getDataFromServer
ständig verwende. Ich bin mir nicht sicher, aber das ist meine bisher beste Idee.
createSpy
( github.com/jasmine/jasmine/blob/… ) mit einem importierten Verweis auf getDataFromServer aus dem Modul 'network.js' zu verwenden. Damit Sie in der Testdatei des Widgets getDataFromServer importieren und dannlet spy = createSpy('getDataFromServer', getDataFromServer)
spyOn
auf diesem Objekt aus demnetwork.js
Modul importieren . Es ist immer ein Verweis auf dasselbe Objekt.Widget
öffentliche Schnittstelle durcheinander bringt .Widget
ist durcheinander ohnedeps
. Warum nicht die Abhängigkeit explizit machen?Antworten:
Ich habe begonnen, den
import * as obj
Stil in meinen Tests zu verwenden, der alle Exporte aus einem Modul als Eigenschaften eines Objekts importiert, das dann verspottet werden kann. Ich finde das viel sauberer als die Verwendung von Rewire oder Proxyquire oder einer ähnlichen Technik. Ich habe dies am häufigsten getan, wenn ich beispielsweise Redux-Aktionen verspotten musste. Folgendes könnte ich für Ihr Beispiel oben verwenden:Wenn Ihre Funktion zufällig ein Standardexport ist,
import * as network from './network'
würde dies produzieren{default: getDataFromServer}
und Sie können network.default verspotten.quelle
import * as obj
nur im Test oder auch in Ihrem regulären Code?[method_name] is not declared writable or has no setter
, was Sinn macht, da die es6-Importe konstant sind. Gibt es eine Möglichkeit, dies zu umgehen?import
(im Gegensatz zurequire
, das überall hingehen kann) wird gehisst, sodass Sie technisch nicht mehrmals importieren können. Klingt so, als würde Ihr Spion woanders angerufen? Um zu verhindern, dass die Tests durcheinander geraten (als Testverschmutzung bezeichnet), können Sie Ihre Spione in einem afterEach (z. B. sinon.sandbox) zurücksetzen. Ich glaube, Jasmine macht das automatisch.import
in ihrem JS schreiben , ES6-Module nicht wirklich verwenden. So etwas wie Webpack oder Babel wird beim Erstellen eingreifen und es entweder in einen eigenen internen Mechanismus zum Aufrufen entfernter Teile des Codes (z. B.__webpack_require__
) oder in einen der De-facto- Standards vor ES6 , CommonJS, AMD oder UMD, konvertieren. Und diese Konvertierung hält sich oft nicht strikt an die Spezifikation. Für viele, viele Entwickler funktioniert diese Antwort jetzt einwandfrei. Zur Zeit.@carpeliam ist korrekt, aber beachten Sie, dass Sie diese Funktion als Teil des Export-Namespace aufrufen müssen, wenn Sie eine Funktion in einem Modul ausspionieren und eine andere Funktion in diesem Modul verwenden möchten, die diese Funktion aufruft. Andernfalls wird der Spion nicht verwendet.
Falsches Beispiel:
Richtiges Beispiel:
quelle
exports.myfunc2
ist ein direkter Verweis auf,myfunc2
bisspyOn
es durch einen Verweis auf eine Spionagefunktion ersetzt wird.spyOn
wird den Wert von ändernexports.myfunc2
und durch ein Spionageobjekt ersetzen, während esmyfunc2
im Bereich des Moduls unberührt bleibt (weilspyOn
es keinen Zugriff darauf hat)*
Sollte das Objekt nicht mit Freeze importiert werden und die Objektattribute können nicht geändert werden?export function
zusammen mit zu verwendenexports.myfunc2
, Commonjs und ES6-Modulsyntax technisch mischt und dies in neueren Versionen von Webpack (2+) nicht zulässig ist, die die Verwendung der ES6-Modulsyntax alles oder nichts erfordern. Ich habe unten eine Antwort hinzugefügt, die auf dieser basiert und in strengen ES6-Umgebungen funktioniert.Ich habe eine Bibliothek implementiert, die versucht, das Problem der Laufzeitverspottung von Typescript-Klassenimporten zu lösen, ohne dass die ursprüngliche Klasse über explizite Abhängigkeitsinjektionen informiert sein muss.
Die Bibliothek verwendet die
import * as
Syntax und ersetzt dann das ursprünglich exportierte Objekt durch eine Stub-Klasse. Die Typensicherheit bleibt erhalten, sodass Ihre Tests zur Kompilierungszeit unterbrochen werden, wenn ein Methodenname aktualisiert wurde, ohne den entsprechenden Test zu aktualisieren.Diese Bibliothek finden Sie hier: ts-mock-imports .
quelle
Die Antwort von @ vdloo brachte mich in die richtige Richtung, aber die gemeinsamen Verwendung der Schlüsselwörter "export" und ES6-Modul "export" in derselben Datei funktionierte nicht für mich (Webpack v2 oder höher beschwert sich). Stattdessen verwende ich einen Standardexport (benannte Variable), der alle Exporte einzelner benannter Module umschließt und dann den Standardexport in meine Testdatei importiert. Ich verwende das folgende Export-Setup mit Mokka / Sinon und Stubbing funktioniert einwandfrei, ohne dass eine Neuverdrahtung erforderlich ist usw.:
quelle
let MyModule
nicht erforderlich ist, um den Standardexport zu verwenden (es kann ein Rohobjekt sein). Außerdem muss diese Methode nichtmyfunc1()
aufgerufen werdenmyfunc2()
, sondern funktioniert nur, um sie direkt auszuspionieren.Ich habe festgestellt, dass diese Syntax funktioniert:
Mein Modul:
Testcode meines Moduls:
Siehe das Dokument .
quelle
jest.mock()
mit dem Namen übereinstimmen, der in import / packge.json anstelle des Namens der Konstante verwendet wird. In den Dokumenten sind beide gleich, aber mit Code wieimport jwt from 'jsonwebtoken'
Sie müssen Sie den Mock alsjest.mock('jsonwebtoken')
Ich habe es nicht selbst versucht, aber ich denke, Spott könnte funktionieren. Sie können das reale Modul durch ein von Ihnen bereitgestelltes Modell ersetzen. Im Folgenden finden Sie ein Beispiel, das Ihnen eine Vorstellung davon gibt, wie es funktioniert:
Es scheint, als würde
mockery
es nicht mehr gewartet und ich denke, es funktioniert nur mit Node.js, aber es ist trotzdem eine nette Lösung, um Module zu verspotten, die sonst schwer zu verspotten sind.quelle