129

テストしたい AMD モジュールがありますが、実際の依存関係をロードする代わりに、その依存関係をモックアウトしたいと考えています。私はrequirejsを使用しており、モジュールのコードは次のようになります。

define(['hurp', 'durp'], function(Hurp, Durp) {
  return {
    foo: function () {
      console.log(Hurp.beans)
    },
    bar: function () {
      console.log(Durp.beans)
    }
  }
}

どうすればモックアウトしてhurpdurp効果的に単体テストを行うことができますか?

4

7 に答える 7

66

したがって、この投稿を読んだ後、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');
      })
    });
  });
})();

そのため、私はこのアプローチを本番環境でしばらく使用していますが、非常に堅牢です。

于 2012-07-27T20:48:34.880 に答える
45

新しいSquire.js libをチェックしてみてください。

ドキュメントから:

Squire.js は、Require.js ユーザーが依存関係を簡単にモックできるようにするための依存関係インジェクターです。

于 2012-12-11T22:41:04.723 に答える
17

私はこの問題に対する3つの異なる解決策を見つけましたが、どれも楽しいものではありません。

依存関係をインラインで定義する

define('hurp', [], function () {
  return {
    beans: 'Beans'
  };
});

define('durp', [], function () {
  return {
    beans: 'durp beans'
  };
});

require('hurpdhurp', function () {
  // test hurpdurp in here
});

醜い。多くの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);

これが私の好ましい解決策です。少し魔法のように見えますが、いくつかの利点があります。

  1. ノードでテストを実行するので、ブラウザーの自動化に煩わされることはありません。
  2. テストでの厄介なAMDボイラープレートの必要性が少なくなります。
  3. あなたevalは怒りで使用するようになり、Crockfordが怒りで爆発するのを想像してください。

明らかに、まだいくつかの欠点があります。

  1. ノードでテストしているため、ブラウザイベントやDOM操作では何もできません。ロジックのテストにのみ適しています。
  2. セットアップするのはまだ少し不格好です。defineテストが実際に実行される場所であるため、すべてのテストでモックアウトする必要があります。

私はこの種のもののためのより良い構文を与えるためにテストランナーに取り組んでいます、しかし私はまだ問題1のための良い解決策を持っていません。

結論

requirejsのdepsをあざけるのは大変です。ある種の機能を見つける方法を見つけましたが、それでもあまり満足していません。もっと良いアイデアがあれば教えてください。

于 2012-07-27T18:48:13.670 に答える
15

config.mapオプションhttp://requirejs.org/docs/api.html#config-mapがあります。

使い方について:

  1. 通常のモジュールを定義します。
  2. スタブ モジュールを定義します。
  3. RequireJS を明示的に構成します。

    requirejs.config({
      map: {
        'source/js': {
          'foo': 'normalModule'
        },
        'source/test': {
          'foo': 'stubModule'
        }
      }
    });
    

この場合、通常のコードとテスト コードの場合、foo実際のモジュール参照とそれに応じてスタブになるモジュールを使用できます。

于 2012-08-01T12:14:49.677 に答える
9

testr.jsを使用して依存関係をモックすることができます。元の依存関係の代わりにモックの依存関係をロードするようにtestrを設定できます。使用例は次のとおりです。

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/

于 2012-08-23T17:56:50.250 に答える
2

この回答は、 Andreas Köberle の回答に基づいています。
彼のソリューションを実装して理解するのは簡単ではなかったので、今後の訪問者に役立つことを期待して、それがどのように機能するか、および避けるべきいくつかの落とし穴についてもう少し詳しく説明します.

まず、セットアップ: Karmaをテスト ランナーとして
使用し、 MochaJsをテスト フレームワークとして使用しています。

Squireのようなものを使用してもうまくいきませんでした。何らかの理由で、それを使用すると、テスト フレームワークがエラーをスローしました。

TypeError: 未定義のプロパティ 'call' を読み取れません

RequireJsには、モジュール ID を他のモジュール IDにマップする可能性があります。また、グローバルとは異なる構成を使用するrequire関数を作成することもできます。 これらの機能は、このソリューションが機能するために不可欠です。require

これは、(たくさんの)コメントを含む私のバージョンのモックコードです(理解できることを願っています)。テストで簡単に要求できるように、モジュール内にラップしました。

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);
                        }
                    });
                });
            });
        });
    });
});
于 2016-08-09T13:08:54.533 に答える