web-dev-qa-db-fra.com

Comment accéder et tester une fonction interne (non-exports) dans un module node.js?

J'essaie de comprendre comment tester les fonctions internes (c'est-à-dire non exportées) dans nodejs (de préférence avec moka ou jasmine). Et je n'en ai aucune idée!

Disons que j'ai un module comme ça:

function exported(i) {
   return notExported(i) + 1;
}

function notExported(i) {
   return i*2;
}

exports.exported = exported;

Et le test suivant (moka):

var assert = require('assert'),
    test = require('../modules/core/test');

describe('test', function(){

  describe('#exported(i)', function(){
    it('should return (i*2)+1 for any given i', function(){
      assert.equal(3, test.exported(1));
      assert.equal(5, test.exported(2));
    });
  });
});

Existe-t-il un moyen de tester par unité la fonction notExported sans l'exporter réellement car elle n'est pas destinée à être exposée?

116
xavier.seignard

Le module rewire est définitivement la solution.

Voici mon code pour accéder à une fonction non exportée et la tester avec Mocha.

application.js:

function logMongoError(){
  console.error('MongoDB Connection Error. Please make sure that MongoDB is running.');
}

test.js:

var rewire = require('rewire');
var chai = require('chai');
var should = chai.should();


var app = rewire('../application/application.js');


logError = app.__get__('logMongoError'); 

describe('Application module', function() {

  it('should output the correct error', function(done) {
      logError().should.equal('MongoDB Connection Error. Please make sure that MongoDB is running.');
      done();
  });
});
170
Antoine

L'astuce consiste à définir la variable d'environnement NODE_ENV à quelque chose comme test, puis à l'exporter de manière conditionnelle.

En supposant que vous n'ayez pas installé globalement mocha, vous pourriez avoir un fichier Makefile à la racine de votre répertoire d'application contenant les éléments suivants:

REPORTER = dot

test:
    @NODE_ENV=test ./node_modules/.bin/mocha \
        --recursive --reporter $(REPORTER) --ui bbd

.PHONY: test

Ce fichier make configure NODE_ENV avant d'exécuter mocha. Vous pouvez ensuite exécuter vos tests mocha avec make test en ligne de commande.

Maintenant, vous pouvez exporter conditionnellement votre fonction qui n’est généralement pas exportée uniquement lorsque vos tests mocha sont en cours d’exécution:

function exported(i) {
   return notExported(i) + 1;
}

function notExported(i) {
   return i*2;
}

if (process.env.NODE_ENV === "test") {
   exports.notExported = notExported;
}
exports.exported = exported;

L'autre réponse a suggéré d'utiliser un module vm pour évaluer le fichier, mais cela ne fonctionne pas et génère une erreur indiquant que les exportations ne sont pas définies.

13
Matthew Bradley

MODIFIER:

Le chargement d'un module à l'aide de vm peut entraîner un comportement inattendu (par exemple, l'opérateur instanceof ne fonctionne plus avec les objets créés dans un tel module car les prototypes globaux sont différents de ceux utilisés dans le module chargé normalement avec require). Je n’utilise plus la technique ci-dessous, mais plutôt le module rewire . Cela fonctionne à merveille. Voici ma réponse originale:

En développant la réponse de srosh ...

C'est un peu hacky, mais j'ai écrit un simple module "test_utils.js" qui devrait vous permettre de faire ce que vous voulez sans avoir d'exportations conditionnelles dans vos modules d'application:

var Script = require('vm').Script,
    fs     = require('fs'),
    path   = require('path'),
    mod    = require('module');

exports.expose = function(filePath) {
  filePath = path.resolve(__dirname, filePath);
  var src = fs.readFileSync(filePath, 'utf8');
  var context = {
    parent: module.parent, paths: module.paths, 
    console: console, exports: {}};
  context.module = context;
  context.require = function (file){
    return mod.prototype.require.call(context, file);};
  (new Script(src)).runInNewContext(context);
  return context;};

Il existe d'autres éléments inclus dans l'objet gobal module d'un module de nœud qui pourraient également devoir être insérés dans l'objet context ci-dessus, mais il s'agit de l'ensemble minimal dont j'ai besoin pour que cela fonctionne.

Voici un exemple d'utilisation de BDD moka:

var util   = require('./test_utils.js'),
    assert = require('assert');

var appModule = util.expose('/path/to/module/modName.js');

describe('appModule', function(){
  it('should test notExposed', function(){
    assert.equal(6, appModule.notExported(3));
  });
});
7
mhess

J'ai trouvé un moyen assez simple qui vous permet de tester, d'espionner et de simuler ces fonctions internal depuis les tests:

Disons que nous avons un module de nœud comme celui-ci:

mymodule.js:
------------
"use strict";

function myInternalFn() {

}

function myExportableFn() {
    myInternalFn();   
}

exports.myExportableFn = myExportableFn;

Si nous voulons maintenant tester et spy et mock myInternalFn sans l'exporter en production nous devons améliorer le fichier de la manière suivante:

my_modified_module.js:
----------------------
"use strict";

var testable;                          // <-- this is new

function myInternalFn() {

}

function myExportableFn() {
    testable.myInternalFn();           // <-- this has changed
}

exports.myExportableFn = myExportableFn;

                                       // the following part is new
if( typeof jasmine !== "undefined" ) {
    testable = exports;
} else {
    testable = {};
}

testable.myInternalFn = myInternalFn;

Maintenant, vous pouvez tester, espionner et simuler une myInternalFn partout où vous l'utilisez comme testable.myInternalFn et en production, il s'agit de non exporté .

1
heinob

En travaillant avec Jasmine, j'ai essayé d'aller plus loin avec la solution proposée par Anthony Mayfield , basée sur rewire .

J'ai implémenté la fonction suivante (Caution: pas encore complètement testé, juste partagé comme stratégie possible)}:

function spyOnRewired() {
    const SPY_OBJECT = "rewired"; // choose preferred name for holder object
    var wiredModule = arguments[0];
    var mockField = arguments[1];

    wiredModule[SPY_OBJECT] = wiredModule[SPY_OBJECT] || {};
    if (wiredModule[SPY_OBJECT][mockField]) // if it was already spied on...
        // ...reset to the value reverted by jasmine
        wiredModule.__set__(mockField, wiredModule[SPY_OBJECT][mockField]);
    else
        wiredModule[SPY_OBJECT][mockField] = wiredModule.__get__(mockField);

    if (arguments.length == 2) { // top level function
        var returnedSpy = spyOn(wiredModule[SPY_OBJECT], mockField);
        wiredModule.__set__(mockField, wiredModule[SPY_OBJECT][mockField]);
        return returnedSpy;
    } else if (arguments.length == 3) { // method
        var wiredMethod = arguments[2];

        return spyOn(wiredModule[SPY_OBJECT][mockField], wiredMethod);
    }
}

Avec une fonction comme celle-ci, vous pouvez espionner les méthodes des objets non exportés et des fonctions de niveau supérieur non exportées, comme suit:

var dbLoader = require("rewire")("../lib/db-loader");
// Example: rewired module dbLoader
// It has non-exported, top level object 'fs' and function 'message'

spyOnRewired(dbLoader, "fs", "readFileSync").and.returnValue(FULL_POST_TEXT); // method
spyOnRewired(dbLoader, "message"); // top level function

Ensuite, vous pouvez définir des attentes comme celles-ci:

expect(dbLoader.rewired.fs.readFileSync).toHaveBeenCalled();
expect(dbLoader.rewired.message).toHaveBeenCalledWith(POST_DESCRIPTION);
1
Franco

Ce n'est pas une pratique recommandée, mais si vous ne pouvez pas utiliser rewire comme suggéré par @Antoine, vous pouvez toujours simplement lire le fichier et utiliser eval().

var fs = require('fs');
const JsFileString = fs.readFileSync(fileAbsolutePath, 'utf-8');
eval(JsFileString);

J'ai trouvé cela utile pendant que l'unité testait les fichiers JS côté client pour un système hérité. 

Les fichiers JS définiraient un grand nombre de variables globales sous window sans instructions require(...) et module.exports (aucun module de combinaison de modules, tel que Webpack ou Browserify, ne permettait de supprimer ces instructions). 

Plutôt que de refactoriser l'intégralité de la base de code, cela nous a permis d'intégrer des tests unitaires dans notre JS côté client.

0
Abhishek Divekar

vous pouvez créer un nouveau contexte en utilisant vm module et évaluer le fichier js qu'il contient, un peu comme le fait repl. alors vous avez accès à tout ce qu'il déclare. 

0
srosh