164 votes

Comment tester un module Node.js qui nécessite d'autres modules et comment simuler la fonction globale de demande ?

Il s'agit d'un exemple trivial qui illustre le cœur 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 pour le test unitaire ? innerLib sans se moquer de la require fonction entièrement ?

Donc, c'est moi qui essaye de me moquer du système global. require et découvrir que ça ne marchera pas même pour faire ça :

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 le require dans la fonction underTest.js Le dossier n'a en fait pas été simulé. Il pointe toujours vers le fichier global require fonction. Il semble donc que je ne puisse que me moquer de la fonction require dans le même fichier que celui dans lequel j'effectue le mocking. Si j'utilise la fonction globale require pour inclure quoi que ce soit, même après que j'ai remplacé la copie locale, les fichiers requis auront toujours l'attribut global require référence.

0 votes

Vous devez écraser global.require . Les variables sont écrites dans module par défaut car les modules ont une portée de module.

0 votes

@Raynos Comment puis-je faire cela ? global.require est indéfini ? Même si je la remplace par ma propre fonction, les autres fonctions ne l'utiliseront jamais, n'est-ce pas ?

185voto

Thorsten Lorenz Points 4419

Vous le pouvez maintenant !

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

Cela signifie que vous devez aucune modification de votre code afin d'injecter des mocks 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 les mocks/stubs pour ses modules requis en une seule étape.

@Raynos a raison de dire que, traditionnellement, il fallait recourir à des solutions pas très idéales pour y parvenir ou faire du développement ascendant à la place.

C'est la raison principale pour laquelle j'ai créé proxyquire - pour permettre le développement piloté par les tests de haut en bas sans aucun problème.

Jetez un coup d'œil à la documentation et aux exemples afin d'évaluer s'ils répondent à vos besoins.

6 votes

J'utilise proxyquire et je ne peux pas en dire assez de bien. Il m'a sauvé ! J'ai été chargé d'écrire des tests jasmine-node pour une application développée dans appcelerator Titanium, ce qui oblige certains modules à être des chemins absolus et à avoir de nombreuses dépendances circulaires. proxyquire m'a permis de les arrêter et d'éliminer le superflu dont je n'avais pas besoin pour chaque test. (Expliqué aquí ). Merci beaucoup !

1 votes

Très bien @ThorstenLorenz, je vais l'utiliser. proxyquire ¡!

0 votes

Ce module est tout simplement incroyable ! Il me rappelle la bibliothèque Moq de .NET.

118voto

Elliot Foster Points 408

Une meilleure option dans ce cas est de simuler les méthodes du module qui est retourné.

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

Vous pouvez tirer parti de cela et utiliser quelque chose comme sinon pour simuler les éléments qui sont nécessaires. moka le test est le suivant :

// 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 est bon intégration avec le chai pour faire des assertions, et j'ai écrit un module pour intégrer sinon avec moka pour permettre un nettoyage plus facile des spy/stub (pour éviter la pollution des tests.)

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

Une autre option consiste à utiliser les mocks Jest. Suivez l'exemple de leur page

0 votes

N'est-ce pas la manière la plus précise de procéder ?

0 votes

Je ne suis pas sûr de ce que vous voulez dire, mais la façon la plus précise de procéder serait de ne rien simuler, mais de permettre l'injection des dépendances. C'est probablement plus facile, cependant. Veuillez me faire savoir si j'ai mal compris votre question.

1 votes

Malheureusement, les modules node.js ne sont PAS garantis comme étant des singletons, comme expliqué ici : justjs.com/posts/

12voto

Kunal Points 42

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

3voto

AlexM Points 1868

Moquerie require ressemble à un méchant piratage pour moi. J'essaierais personnellement de l'éviter et de remanier le code pour le rendre plus testable. Il existe plusieurs 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 faire circuler les dépendances, ce qui peut rendre le code plus compliqué.

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

(Il s'agit d'un exemple artificiel, où l'utilisation de la classe n'est pas raisonnable, mais il transmet 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 pour tester votre code. Le code devient plus verbeux, mais aussi plus facile à tester.

3voto

Jason Sebring Points 4309

Un code simple pour simuler des modules pour les curieux

Remarquez les parties où vous manipulez le require.cache et note require.resolve car c'est 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... jest a cette fonctionnalité intégrée et je recommande ce cadre de test plutôt que de développer le vôtre à des fins de test.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X