226 votes

Comment accéder et tester une fonction interne (non exportée) 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 mocha ou jasmine). Et je n'ai aucune idée !

Disons que j'ai un module comme celui-ci :

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 l'unité notExported sans l'exporter puisqu'elle n'est pas destinée à être exposée ?

290voto

Antoine Points 4345

El rebrancher Le module est définitivement la réponse.

Voici mon code pour accéder à une fonction non exportée et le 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');

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

17voto

Matthew Bradley Points 431

L'astuce consiste à définir le NODE_ENV en quelque chose comme test puis l'exporter de manière conditionnelle.

En supposant que vous n'avez pas installé mocha globalement, vous pourriez avoir un Makefile dans la racine de votre répertoire d'application qui contient ce qui suit :

REPORTER = dot

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

.PHONY: test

Ce fichier make configure le NODE_ENV avant de lancer mocha. Vous pouvez alors exécuter vos tests mocha avec make test à la ligne de commande.

Maintenant, vous pouvez exporter conditionnellement votre fonction qui n'est pas habituellement 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 suggérait 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.

7voto

mhess Points 379

EDITAR:

Chargement d'un module à l'aide de vm peut provoquer un comportement inattendu (par exemple, l'option instanceof ne fonctionne plus avec les objets qui sont créés dans un tel module car les prototypes globaux sont différents de ceux utilisés dans les modules chargés normalement avec require ). Je n'utilise plus la technique ci-dessous et j'utilise plutôt la fonction rebrancher module. Il fonctionne à merveille. Voici ma réponse originale :

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

Cela semble un peu compliqué, 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 y a encore d'autres choses qui sont incluses dans le gobal d'un module de nœud module qui pourrait aussi avoir besoin d'aller dans le context ci-dessus, mais c'est l'ensemble minimum dont j'ai besoin pour que cela fonctionne.

Voici un exemple utilisant mocha BDD :

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

2voto

Franco Points 108

En travaillant avec Jasmine, j'ai essayé d'aller plus en profondeur avec les solution proposée par Anthony Mayfield sur la base de rebrancher .

J'ai implémenté la fonction suivante ( Attention (pas encore testé en profondeur, juste partagé comme une 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 telle fonction, vous pouvez espionner à la fois les méthodes des objets non exportés et les 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 fixer des attentes comme celles-ci :

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

1voto

heinob Points 2081

J'ai trouvé un moyen assez simple qui vous permet de tester, d'espionner et de simuler ces interne à partir des tests :

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

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

function myInternalFn() {

}

function myExportableFn() {
    myInternalFn();   
}

exports.myExportableFn = myExportableFn;

Si nous voulons maintenant tester et espionner et simuler myInternalFn tout en ne l'exportant pas en production nous devons améliorer le dossier comme ceci :

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. myInternalFn partout où vous l'utilisez comme testable.myInternalFn et en production c'est non exporté .

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