Wie teste ich ein Node.js-Modul, für das andere Module erforderlich sind, und wie verspotte ich die globale Anforderungsfunktion?

156

Dies ist ein triviales Beispiel, das den Kern meines Problems veranschaulicht:

var innerLib = require('./path/to/innerLib');

function underTest() {
    return innerLib.doComplexStuff();
}

module.exports = underTest;

Ich versuche, einen Komponententest für diesen Code zu schreiben. Wie kann ich die Anforderung für die innerLibverspotten, ohne die requireFunktion vollständig zu verspotten ?

Ich versuche also, das Globale zu verspotten requireund herauszufinden, dass es nicht einmal funktioniert, das zu tun:

var path = require('path'),
    vm = require('vm'),
    fs = require('fs'),
    indexPath = path.join(__dirname, './underTest');

var globalRequire = require;

require = function(name) {
    console.log('require: ' + name);
    switch(name) {
        case 'connect':
        case indexPath:
            return globalRequire(name);
            break;
    }
};

Das Problem ist, dass die requireFunktion in der underTest.jsDatei tatsächlich nicht verspottet wurde. Es zeigt immer noch auf die globale requireFunktion. Es scheint also, dass ich die requireFunktion nur in derselben Datei verspotten kann, in der ich mich verspotte. Wenn ich die globale Datei verwende require, um etwas einzuschließen, haben die erforderlichen Dateien auch nach dem Überschreiben der lokalen Kopie noch die globale requireReferenz.

Matthew Taylor
quelle
du musst überschreiben global.require. Variablen, auf die modulestandardmäßig geschrieben wird, da Module einen Modulbereich haben.
Raynos
@ Raynos Wie würde ich das machen? global.require ist undefiniert? Selbst wenn ich es durch meine eigene Funktion ersetzen würde, würden andere Funktionen das niemals verwenden, oder?
HMR

Antworten:

174

Du kannst jetzt!

Ich habe Proxyquire veröffentlicht, mit dem sichergestellt wird, dass die globalen Anforderungen in Ihrem Modul überschrieben werden, während Sie es testen.

Dies bedeutet, dass Sie keine Änderungen an Ihrem Code benötigen , um Mocks für die erforderlichen Module einzufügen.

Proxyquire verfügt über eine sehr einfache API, mit der Sie das Modul, das Sie testen möchten, in einem einfachen Schritt auflösen und Mocks / Stubs für die erforderlichen Module weitergeben können.

@Raynos hat Recht, dass man traditionell auf nicht sehr ideale Lösungen zurückgreifen musste, um dies zu erreichen, oder stattdessen eine Bottom-up-Entwicklung durchführen musste

Aus diesem Grund habe ich Proxyquire erstellt, um eine testgetriebene Top-Down-Entwicklung ohne Probleme zu ermöglichen.

Schauen Sie sich die Dokumentation und die Beispiele an, um festzustellen, ob sie Ihren Anforderungen entsprechen.

Thorsten Lorenz
quelle
5
Ich benutze Proxyquire und kann nicht genug gute Dinge sagen. Es hat mich gerettet! Ich wurde beauftragt, Jasminknotentests für eine in Appcelerator Titanium entwickelte App zu schreiben, die einige Module zu absoluten Pfaden und vielen kreisförmigen Abhängigkeiten zwingt. Mit proxyquire kann ich die Lücke schließen und die Cruft verspotten, die ich nicht für jeden Test benötigte. (Erklärung hier ). Vielen Dank!
Sukima
Freut mich zu hören, dass Proxyquire Ihnen geholfen hat, Ihren Code richtig zu testen :)
Thorsten Lorenz
1
sehr schön @ThorstenLorenz, ich werde auf jeden Fall. benutze proxyquire!
Bevacqua
Fantastisch! Als ich die akzeptierte Antwort "Du kannst nicht" sah, dachte ich "Oh Gott, im Ernst?!" aber das hat es wirklich gerettet.
Chadwick
3
Wenn Sie Webpack verwenden, sollten Sie keine Zeit damit verbringen, nach Proxyquire zu suchen. Webpack wird nicht unterstützt. Ich beschäftige mich stattdessen mit Inject -Loader ( github.com/plasticine/inject-loader ).
Artif3x
116

In diesem Fall ist es besser, die Methoden des zurückgegebenen Moduls zu verspotten.

Ob gut oder schlecht, die meisten node.js-Module sind Singletons. Zwei Codeteile, für die () dasselbe Modul erforderlich ist, erhalten denselben Verweis auf dieses Modul.

Sie können dies nutzen und so etwas wie Sinon verwenden , um die benötigten Elemente zu verspotten. Mokka- Test folgt:

// in your testfile
var innerLib  = require('./path/to/innerLib');
var underTest = require('./path/to/underTest');
var sinon     = require('sinon');

describe("underTest", function() {
  it("does something", function() {
    sinon.stub(innerLib, 'toCrazyCrap').callsFake(function() {
      // whatever you would like innerLib.toCrazyCrap to do under test
    });

    underTest();

    sinon.assert.calledOnce(innerLib.toCrazyCrap); // sinon assertion

    innerLib.toCrazyCrap.restore(); // restore original functionality
  });
});

Sinon hat eine gute Integration mit Chai, um Aussagen zu machen, und ich habe ein Modul geschrieben, um Sinon mit Mokka zu integrieren , um die Reinigung von Spionen / Stummeln zu vereinfachen (um Testverschmutzung zu vermeiden).

Beachten Sie, dass underTest nicht auf die gleiche Weise verspottet werden kann, da underTest nur eine Funktion zurückgibt.

Eine andere Möglichkeit ist die Verwendung von Jest Mocks. Folgen Sie auf ihrer Seite

Elliot Foster
quelle
1
Leider ist es NICHT garantiert, dass die Module von node.j
FrontierPsycho
4
@FrontierPsycho ein paar Dinge: Erstens ist der Artikel für das Testen irrelevant. Solange Sie Ihre Abhängigkeiten testen (und nicht Abhängigkeiten von Abhängigkeiten), erhält require('some_module')Ihr gesamter Code dasselbe Objekt zurück, wenn Sie , da Ihr gesamter Code dasselbe Verzeichnis node_modules verwendet. Zweitens verschmilzt der Artikel den Namespace mit Singletons, was irgendwie orthogonal ist. Drittens ist dieser Artikel verdammt alt (was node.js betrifft), sodass das, was damals möglicherweise gültig war, jetzt möglicherweise nicht mehr gültig ist.
Elliot Foster
2
Hm. Wenn einer von uns nicht tatsächlich Code ausgräbt, der den einen oder anderen Punkt beweist, würde ich mich für Ihre Lösung der Abhängigkeitsinjektion entscheiden oder einfach nur Objekte weitergeben. Dies ist sicherer und zukunftssicherer.
FrontierPsycho
1
Ich bin mir nicht sicher, was Sie beweisen wollen. Die Singleton-Natur (zwischengespeichert) von Knotenmodulen wird allgemein verstanden. Abhängigkeitsinjektion ist zwar eine gute Route, kann aber eine ganze Menge mehr Kesselplatte und mehr Code bedeuten. DI ist häufiger in statisch typisierten Sprachen anzutreffen, in denen es schwieriger ist, Spione / Stubs / Mocks dynamisch in Ihren Code einzubringen. Mehrere Projekte, die ich in den letzten drei Jahren durchgeführt habe, verwenden die in meiner obigen Antwort beschriebene Methode. Es ist die einfachste aller Methoden, obwohl ich sie sparsam benutze.
Elliot Foster
1
Ich schlage vor, Sie lesen auf sinon.js nach. Wenn Sie sinon verwenden (wie im obigen Beispiel), würden Sie entweder innerLib.toCrazyCrap.restore()sinub neu starten oder sinon aufrufen, über sinon.stub(innerLib, 'toCrazyCrap')das Sie das Verhalten des Stubs ändern können : innerLib.toCrazyCrap.returns(false). Außerdem scheint die Neuverdrahtung der proxyquireobigen Erweiterung sehr ähnlich zu sein .
Elliot Foster
11

Ich benutze Mock-Require . Stellen Sie sicher, dass Sie Ihre Mocks definieren, bevor Sie requiredas zu testende Modul auswählen.

Kunal
quelle
Es ist auch gut, stop (<Datei>) oder stopAll () sofort zu tun, damit Sie in einem Test, in dem Sie den Mock nicht wollen, keine zwischengespeicherte Datei erhalten.
Justin Kruse
1
Das hat einer Menge geholfen.
Wallop
2

Spott requirefühlt sich für mich wie ein böser Hack an. Ich würde persönlich versuchen, dies zu vermeiden und den Code zu überarbeiten, um ihn testbarer zu machen. Es gibt verschiedene Ansätze, um mit Abhängigkeiten umzugehen.

1) Übergeben Sie Abhängigkeiten als Argumente

function underTest(innerLib) {
    return innerLib.doComplexStuff();
}

Dadurch kann der Code universell getestet werden. Der Nachteil ist, dass Sie Abhängigkeiten weitergeben müssen, wodurch der Code komplizierter aussehen kann.

2) Implementieren Sie das Modul als Klasse und verwenden Sie dann Klassenmethoden / -eigenschaften, um Abhängigkeiten zu erhalten

(Dies ist ein erfundenes Beispiel, bei dem die Verwendung von Klassen nicht sinnvoll ist, aber die Idee vermittelt.) (ES6-Beispiel)

const innerLib = require('./path/to/innerLib')

class underTestClass {
    getInnerLib () {
        return innerLib
    }

    underTestMethod () {
        return this.getInnerLib().doComplexStuff()
    }
}

Jetzt können Sie die getInnerLibMethode zum Testen Ihres Codes einfach stubben . Der Code wird ausführlicher, aber auch einfacher zu testen.

AlexM
quelle
1
Ich denke nicht, dass es hacky ist, wie Sie vermuten ... das ist die Essenz des Verspottens. Das Verspotten erforderlicher Abhängigkeiten macht die Dinge so einfach, dass Entwickler die Kontrolle erhalten, ohne die Codestruktur zu ändern. Ihre Methoden sind zu ausführlich und daher schwer zu überlegen. Ich wähle Proxyrequire oder Mock-Require. Ich sehe hier kein Problem. Code ist sauber und leicht zu verstehen und erinnert sich, dass die meisten Leute, die dies lesen, bereits Code geschrieben haben, den sie komplizieren sollen. Wenn diese Bibliotheken hackisch sind, ist das Verspotten und Stubben nach Ihrer Definition ebenfalls hackisch und sollte gestoppt werden.
Emmanuel Mahuni
1
Das Problem mit Ansatz Nr. 1 besteht darin, dass Sie interne Implementierungsdetails an den Stapel übergeben. Mit mehreren Schichten wird es dann viel komplizierter, ein Verbraucher Ihres Moduls zu sein. Es kann jedoch mit einem IOC-Container-ähnlichen Ansatz funktionieren, sodass Abhängigkeiten automatisch für Sie eingefügt werden. Da jedoch bereits Abhängigkeiten über die imports-Anweisung in Knotenmodule eingefügt wurden, ist es sinnvoll, sie auf dieser Ebene verspotten zu können .
Magritte
1) Dies verschiebt das Problem einfach in eine andere Datei. 2) colorsString.prototype
Lädt
2

Wenn Sie jemals einen Scherz benutzt haben, sind Sie wahrscheinlich mit der Scheinfunktion des Scherzes vertraut.

Mit "jest.mock (...)" können Sie einfach die Zeichenfolge angeben, die in einer require-Anweisung in Ihrem Code irgendwo vorkommen würde, und wenn ein Modul mit dieser Zeichenfolge benötigt wird, wird stattdessen ein Scheinobjekt zurückgegeben.

Beispielsweise

jest.mock("firebase-admin", () => {
    const a = require("mocked-version-of-firebase-admin");
    a.someAdditionalMockedMethod = () => {}
    return a;
})

würde alle Importe / Anforderungen von "firebase-admin" vollständig durch das Objekt ersetzen, das Sie von dieser "Factory" -Funktion zurückgegeben haben.

Nun, Sie können dies tun, wenn Sie Jest verwenden, da Jest eine Laufzeit für jedes Modul erstellt, das ausgeführt wird, und eine "Hooked" -Version von Requirement in das Modul einfügt, aber ohne Jest wäre dies nicht möglich.

Ich habe versucht, dies mit Mock-Require zu erreichen , aber für mich hat es bei verschachtelten Ebenen in meiner Quelle nicht funktioniert. Schauen Sie sich das folgende Problem auf Github an: Mock- Require wird nicht immer mit Mocha aufgerufen .

Um dies zu beheben, habe ich zwei npm-Module erstellt, mit denen Sie das erreichen können, was Sie wollen.

Sie benötigen ein Babel-Plugin und einen Modul-Mocker.

Verwenden Sie in Ihrem .babelrc das babel-plugin-mock-require-Plugin mit folgenden Optionen:

...
"plugins": [
        ["babel-plugin-mock-require", { "moduleMocker": "jestlike-mock" }],
        ...
]
...

und verwenden Sie in Ihrer Testdatei das jestlike-mock-Modul wie folgt:

import {jestMocker} from "jestlike-mock";
...
jestMocker.mock("firebase-admin", () => {
            const firebase = new (require("firebase-mock").MockFirebaseSdk)();
            ...
            return firebase;
});
...

Das jestlike-mockModul ist immer noch sehr rudimentär und hat nicht viel Dokumentation, aber es gibt auch nicht viel Code. Ich schätze alle PRs für einen umfassenderen Funktionsumfang. Das Ziel wäre es, die gesamte Funktion "jest.mock" neu zu erstellen.

Um zu sehen, wie jest implementiert, dass man den Code im "jest-runtime" -Paket nachschlagen kann. Siehe https://github.com/facebook/jest/blob/master/packages/jest-runtime/src/index.js#L734 . Hier wird beispielsweise ein "Automock" eines Moduls generiert.

Hoffentlich hilft das ;)

allesklarbeidir
quelle
1

Das kannst du nicht. Sie müssen Ihre Unit-Test-Suite so aufbauen, dass die niedrigsten Module zuerst getestet werden und die übergeordneten Module, für die Module erforderlich sind, anschließend getestet werden.

Sie müssen auch davon ausgehen, dass Code und node.js von Drittanbietern selbst gut getestet sind.

Ich nehme an, Sie werden in naher Zukunft spöttische Frameworks sehen, die überschreiben global.require

Wenn Sie wirklich einen Mock einfügen müssen, können Sie Ihren Code ändern, um den modularen Bereich verfügbar zu machen.

// underTest.js
var innerLib = require('./path/to/innerLib');

function underTest() {
    return innerLib.toCrazyCrap();
}

module.exports = underTest;
module.exports.__module = module;

// test.js
function test() {
    var underTest = require("underTest");
    underTest.__module.innerLib = {
        toCrazyCrap: function() { return true; }
    };
    assert.ok(underTest());
}

Seien Sie gewarnt, dies wird .__modulein Ihrer API angezeigt und jeder Code kann auf eigene Gefahr auf den modularen Bereich zugreifen.

Raynos
quelle
2
Angenommen, der Code von Drittanbietern ist gut getestet, ist keine gute Möglichkeit, IMO zu arbeiten.
Henry.oswald
5
@beck es ist eine großartige Art zu arbeiten. Es zwingt Sie, nur mit qualitativ hochwertigem Code von Drittanbietern zu arbeiten oder alle Teile Ihres Codes zu schreiben, damit jede Abhängigkeit gut getestet wird
Raynos
Ok, ich dachte, Sie beziehen sich darauf, keine Integrationstests zwischen Ihrem Code und dem Code eines Drittanbieters durchzuführen. Einverstanden.
Henry.oswald
1
Eine "Unit Test Suite" ist nur eine Sammlung von Unit Tests. Die Unit Tests sollten jedoch unabhängig voneinander sein, daher die Einheit im Unit Test. Um verwendbar zu sein, müssen Unit-Tests schnell und unabhängig sein, damit Sie klar erkennen können, wo der Code fehlerhaft ist, wenn ein Unit-Test fehlschlägt.
Andreas Berheim Brudin
Das hat bei mir nicht funktioniert. Das Modulobjekt macht die "var innerLib ..." usw. nicht
verfügbar
1

Sie können die Spottbibliothek verwenden:

describe 'UnderTest', ->
  before ->
    mockery.enable( warnOnUnregistered: false )
    mockery.registerMock('./path/to/innerLib', { doComplexStuff: -> 'Complex result' })
    @underTest = require('./path/to/underTest')

  it 'should compute complex value', ->
    expect(@underTest()).to.eq 'Complex result'
Hirurg103
quelle
1

Einfacher Code zum Verspotten von Modulen für Neugierige

Beachten Sie die Teile, an denen Sie die require.cacheand-Note- require.resolveMethode manipulieren , da dies die geheime Sauce ist.

class MockModules {  
  constructor() {
    this._resolvedPaths = {} 
  }
  add({ path, mock }) {
    const resolvedPath = require.resolve(path)
    this._resolvedPaths[resolvedPath] = true
    require.cache[resolvedPath] = {
      id: resolvedPath,
      file: resolvedPath,
      loaded: true,
      exports: mock
    }
  }
  clear(path) {
    const resolvedPath = require.resolve(path)
    delete this._resolvedPaths[resolvedPath]
    delete require.cache[resolvedPath]
  }
  clearAll() {
    Object.keys(this._resolvedPaths).forEach(resolvedPath =>
      delete require.cache[resolvedPath]
    )
    this._resolvedPaths = {}
  }
}

Verwenden Sie wie :

describe('#someModuleUsingTheThing', () => {
  const mockModules = new MockModules()
  beforeAll(() => {
    mockModules.add({
      // use the same require path as you normally would
      path: '../theThing',
      // mock return an object with "theThingMethod"
      mock: {
        theThingMethod: () => true
      }
    })
  })
  afterAll(() => {
    mockModules.clearAll()
  })
  it('should do the thing', async () => {
    const someModuleUsingTheThing = require('./someModuleUsingTheThing')
    expect(someModuleUsingTheThing.theThingMethod()).to.equal(true)
  })
})

ABER ... Proxyquire ist ziemlich genial und du solltest das benutzen. Es hält Ihre Anforderungsüberschreibungen nur für Tests lokalisiert und ich kann es nur empfehlen.

Jason Sebring
quelle