テストしたいAMDモジュールがありますが、実際の依存関係をロードするのではなく、その依存関係を模擬したいです。私はrequirejsを使用していますが、私のモジュールのコードは次のようになります:
define(['hurp', 'durp'], function(Hurp, Durp) {
return {
foo: function () {
console.log(Hurp.beans)
},
bar: function () {
console.log(Durp.beans)
}
}
}
hurp
とdurp
をモックアウトして、単体テストを効果的に行うにはどうすればよいですか?
この投稿 を読んだ後、requirejs config関数を使用してテスト用の新しいコンテキストを作成し、依存関係を簡単にモックできるソリューションを思い付きました:
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/'
});
}
そのため、Hurp
とDurp
の定義が関数に渡したオブジェクトによって設定される新しいコンテキストが作成されます。名前のMath.randomは少し汚いかもしれませんが、動作します。たくさんのテストがある場合は、モックの再利用を防ぐためにすべてのスイートに新しいコンテキストを作成するか、実際のrequirejsモジュールが必要なときにモックをロードする必要があります。
あなたの場合、次のようになります。
(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');
})
});
});
})();
そのため、私はしばらくの間、このアプローチを実稼働環境で使用していますが、非常に堅牢です。
新しい Squire.js lib をチェックアウトすることをお勧めします
ドキュメントから:
Squire.jsは、Require.jsユーザーが依存関係のモックを簡単にするための依存関係インジェクターです!
私はこの問題に対する3つの異なる解決策を見つけましたが、どれも快適なものではありません。
define('hurp', [], function () {
return {
beans: 'Beans'
};
});
define('durp', [], function () {
return {
beans: 'durp beans'
};
});
require('hurpdhurp', function () {
// test hurpdurp in here
});
Fuい。多くのAMDボイラープレートを使用して、テストを整理する必要があります。
これには、元の依存関係ではなく、モックを指す各依存関係のパスを定義するために、個別のconfig.jsファイルを使用する必要があります。これも見苦しく、大量のテストファイルと構成ファイルを作成する必要があります。
これは私の現在のソリューションですが、それでもひどいものです。
独自のdefine
関数を作成して、モジュールに独自のモックを提供し、テストをコールバックに配置します。次に、次のように、テストを実行するモジュールをeval
します:
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);
これは私の好みのソリューションです。少し魔法のように見えますが、いくつかの利点があります。
eval
を怒りで使用し、Crockfordが怒りで爆発することを想像してください。明らかに、まだいくつかの欠点があります。
define
をモックアウトする必要があります。この種のより良い構文を提供するためにテストランナーに取り組んでいますが、問題1の良い解決策はまだありません。
Requirejsのモックデプスは非常にひどいものです。 sortofが機能する方法を見つけましたが、まだあまり満足していません。もっと良いアイデアがあれば教えてください。
あります config.map
オプション http://requirejs.org/docs/api.html#config-map 。
使い方について:
RequireJSを明示的に構成します。
requirejs.config({
map: {
'source/js': {
'foo': 'normalModule'
},
'source/test': {
'foo': 'stubModule'
}
}
});
この場合、通常のコードとテストコードでは、foo
モジュールを使用できます。これは、実際のモジュール参照であり、それに応じてスタブになります。
testr.js を使用して依存関係をモックできます。元の依存関係の代わりにモック依存関係をロードするようにテスターを設定できます。以下に使用例を示します。
var fakeDep = function(){
this.getText = function(){
return 'Fake Dependancy';
};
};
var Module1 = testr('module1', {
'dependancies/dependancy1':fakeDep
});
これもチェックしてください: http://cyberasylum.janithw.com/mocking-requirejs-dependencies-for-unit-testing/
この回答は、 AndreasKöberleの回答 に基づいています。
。
それで、最初にすべてのセットアップ:
テストランナーとしてKarmaとMochaJsテストフレームワークとして。
Squireのようなものを使用しても機能しませんでした。何らかの理由で、使用したときにテストフレームワークがエラーをスローしました。
TypeError:未定義のプロパティ 'call'を読み取れません
RequireJs は、モジュールIDを他のモジュールIDに map する可能性があります。また、グローバルrequire
よりも 異なる設定 を使用する require
function を作成することもできます。
これらの機能は、このソリューションが機能するために重要です。
ここに、(多くの)コメントを含む、私のバージョンのモックコードを示します(理解できることを望みます)。テストで簡単に要求できるように、モジュール内にラップしました。
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;
});
最大の落とし穴は、文字通り何時間もかかりましたが、RequireJs設定を作成していました。私はそれを(深く)コピーして、必要なプロパティ(コンテキストやマップなど)のみをオーバーライドしようとしました。これは動作しません! baseUrl
のみをコピーしてください。これは正常に機能します。
それを使用するには、テストで必要とし、モックを作成して、createMockRequire
に渡します。例えば:
var ModuleMock = function () {
this.method = function () {
methodCalled += 1;
};
};
var mocks = {
"ModuleIdOrPath": ModuleMock
}
var requireMocks = mocker.createMockRequire(mocks);
そして、ここに完全なテストファイルの例の例:
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);
}
});
});
});
});
});
});
1つのユニットを分離する単純なjsテストを作成する場合は、次のスニペットを使用できます。
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;