Mam moduł AMD, który chcę przetestować, ale zamiast ładować rzeczywiste zależności chcę wyśmiać jego zależności. Używam requirejs, a kod mojego modułu wygląda mniej więcej tak:
define(['hurp', 'durp'], function(Hurp, Durp) {
return {
foo: function () {
console.log(Hurp.beans)
},
bar: function () {
console.log(Durp.beans)
}
}
}
Jak mogę wyszydzać hurp
i durp
efektywnie testować jednostki?
javascript
unit-testing
mocking
requirejs
jergason
źródło
źródło
define
funkcję. Jest jednak kilka różnych opcji. Opublikuję odpowiedź w nadziei, że będzie pomocna.Odpowiedzi:
Po przeczytaniu tego posta wymyśliłem rozwiązanie, które wykorzystuje funkcję konfiguracji requirejs do stworzenia nowego kontekstu dla twojego testu, w którym możesz po prostu mockować swoje zależności:
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/' }); }
Tworzy więc nowy kontekst, w którym definicje
Hurp
iDurp
zostaną ustawione przez obiekty przekazane do funkcji. Math.random dla nazwy jest może trochę brudny, ale działa. Bo jeśli będziesz miał mnóstwo testów, musisz stworzyć nowy kontekst dla każdego pakietu, aby zapobiec ponownemu użyciu twoich mocków lub załadować mocks, gdy potrzebujesz prawdziwego modułu requirejs.W twoim przypadku wyglądałoby to tak:
(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'); }) }); }); })();
Więc używam tego podejścia w produkcji przez jakiś czas i jest naprawdę solidne.
źródło
createContext
funkcji. Więc w twoim przypadku, jeśli przejdziesz tylko{hurp: 'hurp'}
do funkcji,durp
plik zostanie załadowany jako normalna zależność.możesz chcieć sprawdzić nową bibliotekę Squire.js
z dokumentów:
Squire.js to wstrzykiwacz zależności dla użytkowników Require.js, który ułatwia symulowanie zależności!
źródło
Znalazłem trzy różne rozwiązania tego problemu, żadne z nich nie jest przyjemne.
Definiowanie zależności w tekście
define('hurp', [], function () { return { beans: 'Beans' }; }); define('durp', [], function () { return { beans: 'durp beans' }; }); require('hurpdhurp', function () { // test hurpdurp in here });
Fugly. Musisz zaśmiecać swoje testy dużą ilością standardowych schematów AMD.
Ładowanie pozornych zależności z różnych ścieżek
Obejmuje to użycie oddzielnego pliku config.js do zdefiniowania ścieżek dla każdej z zależności, które wskazują na makiety zamiast oryginalnych zależności. Jest to również brzydkie, wymagające stworzenia wielu plików testowych i plików konfiguracyjnych.
Fake It In Node
To jest moje obecne rozwiązanie, ale nadal jest straszne.
Tworzysz własną
define
funkcję, aby dostarczyć własne makiety do modułu i umieścić swoje testy w wywołaniu zwrotnym. Następnieeval
moduł do przeprowadzania testów, na przykład: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);
To jest moje ulubione rozwiązanie. Wygląda trochę magicznie, ale ma kilka zalet.
eval
w gniewie i wyobrazić sobie Crockforda wybuchającego wściekłością.Oczywiście nadal ma pewne wady.
define
w każdym teście, ponieważ to właśnie tam są wykonywane testy.Pracuję nad narzędziem do uruchamiania testów, aby podać ładniejszą składnię dla tego rodzaju rzeczy, ale nadal nie mam dobrego rozwiązania problemu 1.
Wniosek
Wyśmiewanie deps w requirejs jest do bani. Znalazłem sposób, który działa, ale nadal nie jestem z niego zbyt zadowolony. Daj mi znać, jeśli masz jakieś lepsze pomysły.
źródło
Jest
config.map
opcja http://requirejs.org/docs/api.html#config-map .Jak go używać:
Skonfiguruj RequireJS w sposób jawny;
requirejs.config({ map: { 'source/js': { 'foo': 'normalModule' }, 'source/test': { 'foo': 'stubModule' } } });
W takim przypadku dla normalnego i testowego kodu można użyć
foo
modułu, który będzie prawdziwym odniesieniem do modułu i odpowiednio będzie zastępował.źródło
Możesz użyć testr.js do mockowania zależności. Możesz ustawić testr tak, aby ładował makiety zależności zamiast oryginalnych. Oto przykład użycia:
var fakeDep = function(){ this.getText = function(){ return 'Fake Dependancy'; }; }; var Module1 = testr('module1', { 'dependancies/dependancy1':fakeDep });
Sprawdź również: http://cyberasylum.janithw.com/mocking-requirejs-dependencies-for-unit-testing/
źródło
Ta odpowiedź jest oparta na odpowiedzi Andreasa Köberle .
Wdrożenie i zrozumienie jego rozwiązania nie było dla mnie łatwe, więc wyjaśnię je bardziej szczegółowo, jak to działa, oraz kilka pułapek, których należy unikać, mając nadzieję, że pomoże to przyszłym odwiedzającym.
A więc przede wszystkim konfiguracja:
używam Karmy jako narzędzia do uruchamiania testów i MochaJ jako platformy testowej.
Używanie czegoś takiego jak Squire nie działało dla mnie, z jakiegoś powodu, kiedy go używałem, framework testowy wyrzucał błędy:
RequireJs ma możliwość mapowania identyfikatorów modułów na inne identyfikatory modułów. Pozwala również na stworzenie
require
funkcji, która używa innej konfiguracji niż globalnarequire
.Te cechy są kluczowe, aby to rozwiązanie działało.
Oto moja wersja kodu próbnego, w tym (dużo) komentarzy (mam nadzieję, że jest to zrozumiałe). Owinąłem go wewnątrz modułu, aby testy mogły go łatwo wymagać.
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; });
Największa pułapka natknąłem, który kosztował mnie dosłownie godzinami, było stworzenie config RequireJs. Próbowałem (głęboko) go skopiować i zastąpić tylko niezbędne właściwości (takie jak kontekst lub mapa). To nie działa! Skopiuj tylko
baseUrl
, to działa dobrze.Stosowanie
Aby z niego skorzystać, wymagaj go w swoim teście, utwórz makiety, a następnie przekaż go
createMockRequire
. Na przykład:var ModuleMock = function () { this.method = function () { methodCalled += 1; }; }; var mocks = { "ModuleIdOrPath": ModuleMock } var requireMocks = mocker.createMockRequire(mocks);
A tutaj przykład pełnego pliku testowego :
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); } }); }); }); }); }); });
źródło
jeśli chcesz wykonać zwykłe testy js, które izolują jedną jednostkę, możesz po prostu użyć tego fragmentu:
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;
źródło