it-swarm.com.de

Wie kann ich Abhängigkeiten für Komponententests in RequireJS nachahmen?

Ich habe ein AMD-Modul, das ich testen möchte, aber ich möchte seine Abhängigkeiten ausspotten, anstatt die tatsächlichen Abhängigkeiten zu laden. Ich verwende requirejs und der Code für mein Modul sieht ungefähr so ​​aus:

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

Wie kann ich hurp und durp verspotten, damit ich effektiv einen Komponententest durchführen kann?

126
jergason

Nach dem Lesen von diesem Beitrag habe ich eine Lösung gefunden, die mithilfe der Funktion requirejs config einen neuen Kontext für Ihren Test erstellt, in dem Sie einfach Ihre Abhängigkeiten verspotten können:

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/'
  });
}

So wird ein neuer Kontext erstellt, in dem die Definitionen für Hurp und Durp durch die Objekte festgelegt werden, die Sie an die Funktion übergeben haben. Das Math.Random für den Namen ist vielleicht etwas schmutzig, aber es funktioniert. Denn wenn Sie eine Reihe von Tests haben, müssen Sie für jede Suite einen neuen Kontext erstellen, um die Wiederverwendung Ihrer Mocks zu verhindern, oder um Mocks zu laden, wenn Sie das Modul real requirejs benötigen.

In deinem Fall würde es so aussehen:

(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');
      })
    });
  });
})();

Ich verwende diesen Ansatz für eine Weile in der Produktion und er ist wirklich robust.

64

vielleicht möchten Sie die neue Squire.js lib

aus den Dokumenten:

Squire.js ist ein Abhängigkeitsinjektor für Require.js-Benutzer, mit dem es einfach ist, Abhängigkeiten zu verspotten!

44
busticated

Ich habe drei verschiedene Lösungen für dieses Problem gefunden, keine davon ist angenehm.

Abhängigkeiten inline definieren

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

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

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

Fugly. Sie müssen Ihre Tests mit viel AMD-Boilerplate überfrachten.

Laden von Scheinabhängigkeiten aus verschiedenen Pfaden

Dazu wird eine separate Datei config.js verwendet, um Pfade für jede der Abhängigkeiten zu definieren, die auf Verspottungen anstelle der ursprünglichen Abhängigkeiten verweisen. Dies ist auch hässlich und erfordert die Erstellung von Tonnen von Testdateien und Konfigurationsdateien.

Fake It In Node

Dies ist meine aktuelle Lösung, aber immer noch eine schreckliche.

Sie erstellen Ihre eigene define -Funktion, um dem Modul Ihre eigenen Mocks zur Verfügung zu stellen und Ihre Tests in den Rückruf einzutragen. Dann eval Sie das Modul, um Ihre Tests auszuführen, wie folgt:

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);

Dies ist meine bevorzugte Lösung. Es sieht ein wenig magisch aus, hat aber ein paar Vorteile.

  1. Führen Sie Ihre Tests in Node aus, damit Sie keine Probleme mit der Browser-Automatisierung haben.
  2. Bei Ihren Tests wird weniger unordentliches AMD-Boilerplate benötigt.
  3. Sie können eval im Zorn verwenden und sich Crockford vorstellen, der vor Wut explodiert.

Es hat natürlich noch einige Nachteile.

  1. Da Sie in Node testen, können Sie mit Browserereignissen oder DOM-Manipulation nichts anfangen. Nur gut zum Testen der Logik.
  2. Noch etwas klobig einzurichten. Sie müssen define in jedem Test verspotten, da dort Ihre Tests tatsächlich ausgeführt werden.

Ich arbeite an einem Testläufer, um eine bessere Syntax für diese Art von Dingen zu finden, aber ich habe immer noch keine gute Lösung für Problem 1.

Fazit

Das Verspotten von Deps in Requireds ist zum Kotzen. Ich habe einen Weg gefunden, der irgendwie funktioniert, bin aber immer noch nicht sehr zufrieden damit. Bitte lassen Sie mich wissen, wenn Sie bessere Ideen haben.

16
jergason

Da ist ein config.map Option http://requirejs.org/docs/api.html#config-map .

Wie man es benutzt:

  1. Normalmodul definieren;
  2. Stub-Modul definieren;
  3. Konfigurieren Sie RequireJS explizit.

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

In diesem Fall können Sie für normalen Code und Testcode das foo -Modul verwenden, das eine echte Modulreferenz darstellt und dementsprechend stubgt.

15
Artem Oboturov

Sie können testr.js verwenden, um Abhängigkeiten zu verspotten. Sie können testr so einstellen, dass die Scheinabhängigkeiten anstelle der ursprünglichen geladen werden. Hier ist ein Anwendungsbeispiel:

var fakeDep = function(){
    this.getText = function(){
        return 'Fake Dependancy';
    };
};

var Module1 = testr('module1', {
    'dependancies/dependancy1':fakeDep
});

Überprüfen Sie dies auch: http://cyberasylum.janithw.com/mocking-requirejs-dependencies-for-unit-testing/

9
janith

Diese Antwort basiert auf Andreas Köberles Antwort .
Es war nicht so einfach für mich, seine Lösung zu implementieren und zu verstehen, deshalb erkläre ich sie etwas detaillierter und vermeide einige Fallstricke, in der Hoffnung, dass sie zukünftigen Besuchern helfen wird.

Also zunächst das Setup:
Ich benutze Karma als Testläufer und MochaJs als Testframework.

Die Verwendung von etwas wie Squire hat bei mir aus irgendeinem Grund nicht funktioniert, als ich es verwendet habe, hat das Testframework Fehler ausgegeben:

TypeError: Eigenschaft 'call' von undefined kann nicht gelesen werden

RequireJs hat die Möglichkeit, map Modul-IDs auf andere Modul-IDs abzubilden. Sie können auch eine require -Funktion erstellen , die eine andere Konfiguration verwendet als die globale require.
Diese Funktionen sind für die Funktionsfähigkeit dieser Lösung von entscheidender Bedeutung.

Hier ist meine Version des Mock-Codes, einschließlich (vieler) Kommentare (ich hoffe, es ist verständlich). Ich habe es in ein Modul eingewickelt, damit die Tests es leicht erfordern können.

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;
});

Die größte Falle , die mich buchstäblich Stunden gekostet hat, war das Erstellen der RequireJs-Konfiguration. Ich habe versucht, es (tief) zu kopieren und nur die notwendigen Eigenschaften (wie Kontext oder Karte) zu überschreiben. Das funktioniert nicht! Kopieren Sie nur das baseUrl, das funktioniert einwandfrei.

Verwendung

Um es zu verwenden, fordern Sie es in Ihrem Test an, erstellen Sie die Mocks und übergeben Sie es dann an createMockRequire. Beispielsweise:

var ModuleMock = function () {
    this.method = function () {
        methodCalled += 1;
    };
};
var mocks = {
    "ModuleIdOrPath": ModuleMock
}
var requireMocks = mocker.createMockRequire(mocks);

Und hier ein Beispiel einer vollständigen Testdatei :

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);
                        }
                    });
                });
            });
        });
    });
});
2
Domysee

wenn Sie einige einfache js-Tests durchführen möchten, die eine Einheit isolieren, können Sie einfach dieses Snippet verwenden:

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;
0
user3033599