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?
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();
});
});
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.
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));
});
});
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é .
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);
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.
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.