javascript - Hur kan jag mocka beroenden för enhetstestning i RequireJS?
Jag har en AMD-modul som jag vill testa, men jag vill mocka dess beroenden istället för att ladda de faktiska beroendena. Jag använder requirejs, och koden för min modul ser ungefär ut så här:
define(['hurp', 'durp'], function(Hurp, Durp) {
return {
foo: function () {
console.log(Hurp.beans)
},
bar: function () {
console.log(Durp.beans)
}
}
}
Hur kan jag mocka ut hurp och durp så att jag effektivt kan enhetstesta?
Svar #1
Så efter att ha läst det här inlägget kom jag på en lösning som använder requirejs-konfigurationsfunktionen för att skapa en ny kontext för ditt test där du enkelt kan mocka dina beroenden:
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/'
});
}
Så det skapar en ny kontext där definitionerna för Hurp och Durp kommer att sättas av de objekt du skickade in i funktionen. Math.random för namnet är kanske lite skumt men det fungerar. För om du har en massa tester behöver du skapa ny kontext för varje svit för att förhindra att du återanvänder dina mockar, eller för att ladda mockar när du vill ha den riktiga requirejs-modulen.
I ditt fall skulle det se ut så här:
(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');
})
});
});
})();
Så jag har använt detta tillvägagångssätt i produktion ett tag och det är verkligen robust.
Svar #2
du kanske vill kolla in det nya Squire.js-biblioteket
från dokumentationen:
Squire.js är en beroendeinjektor för Require.js-användare för att göra det enkelt att mocka beroenden!
Svar #3
Jag har hittat tre olika lösningar på detta problem, ingen av dem trevlig.
Definiera beroenden direkt
define('hurp', [], function () {
return {
beans: 'Beans'
};
});
define('durp', [], function () {
return {
beans: 'durp beans'
};
});
require('hurpdhurp', function () {
// test hurpdurp in here
});
Fult. Du måste fylla dina tester med massor av AMD-standardkod.
Laddar simulerade beroenden från olika sökvägar
Detta innebär att använda en separat config.js-fil för att definiera sökvägar för varje beroende som pekar på mockar istället för de ursprungliga beroendena. Detta är också fult och kräver skapandet av massor av testfiler och konfigurationsfiler.
Låtsas i Node
Detta är min nuvarande lösning, men den är fortfarande en dålig en.
Du skapar din egen define-funktion för att tillhandahålla dina egna mockar till modulen och lägger dina tester i callbacken. Sedan eval du modulen för att köra dina tester, på följande sätt:
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);
Detta är min föredragna lösning. Det ser lite magiskt ut, men det har några fördelar.
- Kör dina tester i Node, så att du slipper hålla på med webbläsarautomatisering.
- Mindre behov av kladdig AMD-standardkod i dina tester.
- Du får använda
evali vrede, och föreställ dig Crockford explodera av ilska.
Det har fortfarande några nackdelar, uppenbarligen.
- Eftersom du testar i node kan du inte göra någonting med browserhändelser eller DOM-manipulation. Bara bra för att testa logik.
- Ännu lite klumpigt att ställa in. Du behöver mocka
definei varje test, eftersom det är där dina tester faktiskt körs.
Jag jobbar på en testrunner för att ge en trevligare syntax för den här typen av saker, men jag har fortfarande ingen bra lösning för problem 1.
Slutsats
Att mocka beroenden i requirejs suger verkligen. Jag hittade ett sätt som på sätt och vis fungerar, men jag är fortfarande inte särskilt nöjd med det. Vänligen låt mig veta om du har några bättre idéer.
Svar #4
Det finns ett config.map-alternativ http://requirejs.org/docs/api.html#config-map.
Om hur man använder det:
- Definiera normal modul;
- Definiera stubmodul;
Konfigurera RequireJS uttryckligen;
requirejs.config({ map: { 'source/js': { 'foo': 'normalModule' }, 'source/test': { 'foo': 'stubModule' } } });
I detta fall för normal och testkod kan du använda foo-modulen som kommer att vara en riktig modulreferens och stubbas därefter.
