web-dev-qa-db-fra.com

Comment tester à l'unité un module node.js nécessitant d'autres modules

Voici un exemple trivial qui illustre le noeud de mon problème:

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

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

module.exports = underTest;

J'essaie d'écrire un test unitaire pour ce code. Comment puis-je simuler l'exigence de innerLib sans se moquer de la fonction require?

EDIT: C’est donc moi qui essaie de me moquer de l’exigence mondiale et de découvrir que cela ne fonctionnera même pas:

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

Le problème est que la fonction require à l'intérieur du fichier underTest.js n'a en réalité pas été créée. Il pointe toujours vers la fonction globale require. Il semble donc que je ne peux que me moquer de la fonction require dans le même fichier que je me moque. Si j'utilise la variable globale require pour inclure quoi que ce soit, même après avoir remplacé la copie locale, les fichiers requis auront toujours la référence globale require.

137
Matthew Taylor

Tu peux maintenant!

J'ai publié proxyquire qui se chargera de remplacer le besoin global dans votre module pendant que vous le testez.

Cela signifie que vous avez besoin de aucune modification de votre code pour pouvoir injecter des simulacres pour les modules requis.

Proxyquire a une API très simple qui permet de résoudre le module que vous essayez de tester et de transmettre des mocks/stubs pour ses modules requis en une seule étape.

@Raynos a raison de dire que traditionnellement, il fallait recourir à des solutions peu idéales pour y parvenir ou à un développement partant de la base.

C’est la raison principale pour laquelle j’ai créé proxyquire - pour permettre un développement basé sur des tests de haut en bas, sans tracas.

Consultez la documentation et les exemples afin de déterminer si cela répondra à vos besoins.

164
Thorsten Lorenz

Dans ce cas, une meilleure option consiste à simuler les méthodes du module renvoyé.

Pour le meilleur ou pour le pire, la plupart des modules node.js sont des singletons; deux morceaux de code qui nécessitent () le même module obtiennent la même référence à ce module.

Vous pouvez en tirer parti et utiliser quelque chose comme sinon pour simuler les éléments nécessaires. moka le test suit:

// 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', 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 a bien intégration avec chai pour faire des assertions, et j’ai écrit un module pour intégrer sinon avec mocha pour permettre un nettoyage plus facile des espions/bouts (pour éviter la pollution test.)

Notez que underTest ne peut pas être simulé de la même manière, car underTest ne renvoie qu'une fonction.

105
Elliot Foster

J'utilise mock-require . Assurez-vous de définir vos modèles avant require le module à tester.

9
Kunal

require se moque de moi. Personnellement, j'essayerais de l'éviter et de refactoriser le code pour le rendre plus testable ... Il existe différentes approches pour gérer les dépendances.

1) passer les dépendances comme arguments

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

Cela rendra le code universellement testable. L'inconvénient est que vous devez éviter les dépendances, ce qui peut rendre le code plus compliqué.

2) implémenter le module en tant que classe, puis utiliser des méthodes/propriétés de classe pour obtenir des dépendances

(Ceci est un exemple artificiel, où l’utilisation de la classe n’est pas raisonnable, mais elle traduit l’idée) (Exemple ES6)

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

class underTestClass {
    getInnerLib () {
        return innerLib
    }

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

Maintenant, vous pouvez facilement stub getInnerLib méthode pour tester votre code. Le code devient plus bavard, mais aussi plus facile à tester.

1
AlexM

Vous pouvez utiliser mockery library:

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'
1
Hirurg103

Tu ne peux pas. Vous devez créer votre suite de tests unitaires de sorte que les modules les plus bas soient d'abord testés, puis que les modules de niveau supérieur nécessitant des modules le soient ensuite.

Vous devez également supposer que tout code tiers et node.js sont bien testés. 

Je suppose que vous allez voir arriver prochainement des frameworks moqueurs qui écrasent global.require

Si vous devez réellement injecter une maquette, vous pouvez modifier votre code pour exposer l'étendue modulaire.

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

Soyez averti que ceci expose .__module dans votre API et tout code peut accéder à la portée modulaire à son propre danger.

1
Raynos

Code simple pour simuler des modules pour les curieux

Notez les parties sur lesquelles vous manipulez la méthode require.cache et notez la méthode require.resolve car il s'agit de la sauce secrète.

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 = {}
  }
}

Utilisez comme :

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

MAIS ... proxyquire est assez génial et vous devriez l'utiliser. Il conserve vos demandes de remplacement localisées aux tests uniquement et je le recommande vivement.

0
Jason Sebring

Si vous avez déjà utilisé la plaisanterie, alors vous connaissez probablement la fonctionnalité fictive de la plaisanterie.

Avec "jest.mock (...)", vous pouvez simplement spécifier la chaîne qui apparaîtrait dans une instruction require de votre code quelque part et à chaque fois qu'un module est requis à l'aide de cette chaîne, un objet fictif est renvoyé à la place.

Par exemple 

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

remplacerait complètement toutes les importations/demandes de "firebase-admin" par l'objet que vous avez renvoyé de cette fonction "factory".

Eh bien, vous pouvez le faire lorsque jest est utilisé, car jest crée une exécution autour de chaque module qu’il exécute et injecte une version "accrochée" de require dans le module, mais vous ne pourriez pas le faire sans plaisanter.

J'ai essayé d'y parvenir avec mock-require mais pour moi, cela ne fonctionnait pas pour les niveaux imbriqués dans ma source. Examinez le problème suivant sur github: mock-require pas toujours appelé avec Mocha .

Pour remédier à cela, j'ai créé deux modules npm que vous pouvez utiliser pour obtenir ce que vous voulez.

Vous avez besoin d’un plug-in babel et d’un module mocker.

Dans votre .babelrc, utilisez le plugin babel-plugin-mock-require avec les options suivantes:

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

et dans votre fichier de test, utilisez le module jestlike-mock comme ceci:

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

Le module jestlike-mock est toujours très rudimentaire et n’a pas beaucoup de documentation mais il n’ya pas beaucoup de code non plus. J'apprécie les relations publiques pour un ensemble de fonctionnalités plus complet. L'objectif serait de recréer l'intégralité de la fonctionnalité "jest.mock".

Afin de voir comment jest implémente cela, on peut rechercher le code dans le paquet "jest-runtime". Voir https://github.com/facebook/jest/blob/master/packages/jest-runtime/src/index.js#L734 par exemple, ils génèrent ici un "automock" d'un module.

J'espère que cela pourra aider ;)

0
allesklarbeidir