Je souhaite tester un module AMD, mais je veux simuler ses dépendances au lieu de charger les dépendances réelles. J'utilise requirejs, et le code de mon module ressemble à ceci:
define(['hurp', 'durp'], function(Hurp, Durp) {
return {
foo: function () {
console.log(Hurp.beans)
},
bar: function () {
console.log(Durp.beans)
}
}
}
Comment puis-je simuler hurp
et durp
afin de pouvoir effectuer des tests unitaires?
Donc, après avoir lu cet article , je suis arrivé à une solution qui utilise la fonction de configuration requirejs pour créer un nouveau contexte pour votre test où vous pouvez simplement vous moquer de vos dépendances:
var cnt = 0;
function createContext(stubs) {
cnt++;
var map = {};
var i18n = stubs.i18n;
stubs.i18n = {
load: sinon.spy(function(name, req, onLoad) {
onLoad(i18n);
})
};
_.each(stubs, function(value, key) {
var stubName = 'stub' + key + cnt;
map[key] = stubName;
define(stubName, function() {
return value;
});
});
return require.config({
context: "context_" + cnt,
map: {
"*": map
},
baseUrl: 'js/cfe/app/'
});
}
Cela crée donc un nouveau contexte dans lequel les définitions de Hurp
et Durp
seront définies par les objets que vous avez passés dans la fonction. Le nom Math.random est peut-être un peu sale, mais ça marche. Parce que si vous avez un tas de tests, vous devez créer un nouveau contexte pour chaque suite pour éviter de réutiliser vos simulacres, ou pour charger des simulacres lorsque vous voulez le module requirejs réel.
Dans votre cas, cela ressemblerait à ceci:
(function () {
var stubs = {
hurp: 'hurp',
durp: 'durp'
};
var context = createContext(stubs);
context(['yourModuleName'], function (yourModule) {
//your normal jasmine test starts here
describe("yourModuleName", function () {
it('should log', function(){
spyOn(console, 'log');
yourModule.foo();
expect(console.log).toHasBeenCalledWith('hurp');
})
});
});
})();
Donc, j'utilise cette approche en production depuis un moment et c'est vraiment robuste.
vous voudrez peut-être consulter le nouveau Squire.js lib
des docs:
Squire.js est un injecteur de dépendances permettant aux utilisateurs de Require.js de simplifier les dépendances moqueuses!
J'ai trouvé trois solutions différentes à ce problème, aucune d'entre elles n'étant agréable.
define('hurp', [], function () {
return {
beans: 'Beans'
};
});
define('durp', [], function () {
return {
beans: 'durp beans'
};
});
require('hurpdhurp', function () {
// test hurpdurp in here
});
Fugly. Vous devez encombrer vos tests avec de nombreuses techniques AMD.
Cela implique l'utilisation d'un fichier séparé config.js pour définir des chemins pour chacune des dépendances qui pointent vers des alarmes au lieu des dépendances d'origine. C'est également moche, nécessitant la création de tonnes de fichiers de test et de fichiers de configuration.
Ceci est ma solution actuelle, mais est toujours une terrible.
Vous créez votre propre fonction define
pour fournir vos propres modèles au module et mettre vos tests dans le rappel. Ensuite, vous eval
le module pour exécuter vos tests, comme ceci:
var fs = require('fs')
, hurp = {
beans: 'BEANS'
}
, durp = {
beans: 'durp beans'
}
, hurpDurp = fs.readFileSync('path/to/hurpDurp', 'utf8');
;
function define(deps, cb) {
var TestableHurpDurp = cb(hurp, durp);
// now run tests below on TestableHurpDurp, which is using your
// passed-in mocks as dependencies.
}
// evaluate the AMD module, running your mocked define function and your tests.
eval(hurpDurp);
Ceci est ma solution préférée. Cela semble un peu magique, mais présente quelques avantages.
eval
dans la colère et imaginez Crockford exploser de rage.Il y a toujours des inconvénients, évidemment.
define
dans chaque test, car c’est là que s’effectuent réellement vos tests.Je travaille sur un coureur de test pour donner une syntaxe plus agréable pour ce genre de choses, mais je n'ai toujours pas de bonne solution pour le problème 1.
Mocking Deps dans requirejs suce dur. J'ai trouvé un moyen qui fonctionne, mais je n'en suis toujours pas très heureux. S'il vous plaît laissez-moi savoir si vous avez de meilleures idées.
Il y a un config.map
option http://requirejs.org/docs/api.html#config-map .
Sur comment l'utiliser:
Configurez RequireJS de manière explicite;
requirejs.config({
map: {
'source/js': {
'foo': 'normalModule'
},
'source/test': {
'foo': 'stubModule'
}
}
});
Dans ce cas, pour le code normal et le code de test, vous pouvez utiliser le module foo
qui sera la référence du module réel et le stub correspondant.
Vous pouvez utiliser testr.js pour simuler des dépendances. Vous pouvez configurer testr pour charger les dépendances factices au lieu de celles d'origine. Voici un exemple d'utilisation:
var fakeDep = function(){
this.getText = function(){
return 'Fake Dependancy';
};
};
var Module1 = testr('module1', {
'dependancies/dependancy1':fakeDep
});
Regardez aussi ceci: http://cyberasylum.janithw.com/mocking-requirejs-dependencies-for-unit-testing/
Cette réponse est basée sur réponse d'Andreas Köberle .
Ce n’était pas si facile pour moi d’implémenter et de comprendre sa solution. Je vais donc expliquer un peu plus en détail comment cela fonctionne, ainsi que certains pièges à éviter, en espérant que cela aidera les futurs visiteurs.
Donc, tout d’abord, la configuration:
J'utilise le Karma comme coureur de test et MochaJs comme cadre de test.
Utiliser quelque chose comme Squire n'a pas fonctionné pour moi. Pour une raison quelconque, lorsque je l'ai utilisé, le cadre de test a généré des erreurs:
TypeError: Impossible de lire la propriété 'appel' d'undefined
RequireJs a la possibilité de mapper identifiants de module avec d'autres identifiants de module. Cela permet également de créer une fonction require
qui utilise un configuration différente que le global require
.
Ces fonctionnalités sont essentielles au bon fonctionnement de cette solution.
Voici ma version du code factice, y compris (beaucoup) des commentaires (j'espère que c'est compréhensible). Je l'ai enveloppé dans un module pour que les tests puissent l'exiger facilement.
define([], function () {
var count = 0;
var requireJsMock= Object.create(null);
requireJsMock.createMockRequire = function (mocks) {
//mocks is an object with the module ids/paths as keys, and the module as value
count++;
var map = {};
//register the mocks with unique names, and create a mapping from the mocked module id to the mock module id
//this will cause RequireJs to load the mock module instead of the real one
for (property in mocks) {
if (mocks.hasOwnProperty(property)) {
var moduleId = property; //the object property is the module id
var module = mocks[property]; //the value is the mock
var stubId = 'stub' + moduleId + count; //create a unique name to register the module
map[moduleId] = stubId; //add to the mapping
//register the mock with the unique id, so that RequireJs can actually call it
define(stubId, function () {
return module;
});
}
}
var defaultContext = requirejs.s.contexts._.config;
var requireMockContext = { baseUrl: defaultContext.baseUrl }; //use the baseUrl of the global RequireJs config, so that it doesn't have to be repeated here
requireMockContext.context = "context_" + count; //use a unique context name, so that the configs dont overlap
//use the mapping for all modules
requireMockContext.map = {
"*": map
};
return require.config(requireMockContext); //create a require function that uses the new config
};
return requireJsMock;
});
Le plus gros piège que j'ai rencontré, qui m'a coûté littéralement des heures, était la création de la configuration RequireJs. J'ai essayé de le copier (profondément) et de ne remplacer que les propriétés nécessaires (comme le contexte ou la carte). Cela ne fonctionne pas! Copiez seulement le baseUrl
, cela fonctionne bien.
Pour l'utiliser, exigez-le dans votre test, créez les simulacres, puis transmettez-le à createMockRequire
. Par exemple:
var ModuleMock = function () {
this.method = function () {
methodCalled += 1;
};
};
var mocks = {
"ModuleIdOrPath": ModuleMock
}
var requireMocks = mocker.createMockRequire(mocks);
Et voici un exemple d'un fichier de test complet :
define(["chai", "requireJsMock"], function (chai, requireJsMock) {
var expect = chai.expect;
describe("Module", function () {
describe("Method", function () {
it("should work", function () {
return new Promise(function (resolve, reject) {
var handler = { handle: function () { } };
var called = 0;
var moduleBMock = function () {
this.method = function () {
methodCalled += 1;
};
};
var mocks = {
"ModuleBIdOrPath": moduleBMock
}
var requireMocks = requireJsMock.createMockRequire(mocks);
requireMocks(["js/ModuleA"], function (moduleA) {
try {
moduleA.method(); //moduleA should call method of moduleBMock
expect(called).to.equal(1);
resolve();
} catch (e) {
reject(e);
}
});
});
});
});
});
});
si vous voulez faire des tests simples js qui isolent une unité, vous pouvez simplement utiliser cet extrait:
function define(args, func){
if(!args.length){
throw new Error("please stick to the require.js api which wants a: define(['mydependency'], function(){})");
}
var fileName = document.scripts[document.scripts.length-1].src;
// get rid of the url and path elements
fileName = fileName.split("/");
fileName = fileName[fileName.length-1];
// get rid of the file ending
fileName = fileName.split(".");
fileName = fileName[0];
window[fileName] = func;
return func;
}
window.define = define;