web-dev-qa-db-fra.com

moqueur window.location.href en Javascript

J'ai quelques tests unitaires pour une fonction qui utilise le window.location.href - pas l'idéal, je l'aurais de loin passé, mais ce n'est pas possible dans l'implémentation. Je me demande simplement s'il est possible de simuler cette valeur sans que ma page de test runner se dirige réellement vers l'URL.

  window.location.href = "http://www.website.com?varName=foo";    
  expect(actions.paramToVar(test_Data)).toEqual("bar"); 

J'utilise jasmine pour mon framework de tests unitaires.

55
wmitchell

Vous devez simuler le contexte local et créer votre propre version des objets window et window.location

var localContext = {
    "window":{
        location:{
            href: "http://www.website.com?varName=foo"
        }
    }
}

// simulated context
with(localContext){
    console.log(window.location.href);
    // http://www.website.com?varName=foo
}

//actual context
console.log(window.location.href);
// http://www.actual.page.url/...

Si vous utilisez with, toutes les variables (y compris window!) Seront d'abord examinées à partir de l'objet context et si elles ne sont pas présentes, à partir du contexte réel.

6
Andris

La meilleure façon de faire est de créer une fonction d'assistance quelque part puis de se moquer de cela:

 var mynamespace = mynamespace || {};
    mynamespace.util = (function() {
      function getWindowLocationHRef() {
          return window.location.href;
      }
      return { 
        getWindowLocationHRef: getWindowLocationHRef
      }
    })();

Maintenant, au lieu d'utiliser window.location.href directement dans votre code, utilisez simplement ceci à la place. Ensuite, vous pouvez remplacer cette méthode chaque fois que vous devez renvoyer une valeur simulée: 

mynamespace.util.getWindowLocationHRef = function() {
  return "http://mockhost/mockingpath" 
};

Si vous souhaitez une partie spécifique de l'emplacement de la fenêtre telle qu'un paramètre de chaîne de requête, créez également des méthodes d'assistance pour cela également et conservez l'analyse de votre code principal. Certains frameworks tels que jasmine ont des espions de test qui peuvent non seulement se moquer de la fonction pour renvoyer les valeurs souhaitées, mais peuvent également vérifier qu'elle s'appelle:

spyOn(mynamespace.util, 'getQueryStringParameterByName').andReturn("desc");
//...
expect(mynamespace.util.getQueryStringParameterByName).toHaveBeenCalledWith("sort");
47
Kurt Harriger

Je proposerais deux solutions qui ont déjà été évoquées dans les messages précédents ici:

  • Créez une fonction autour de l'accès, utilisez-la dans votre code de production et associez-la à Jasmine dans vos tests:

    var actions = {
        getCurrentURL: function () {
            return window.location.href;
        },
        paramToVar: function (testData) {
            ...
            var url = getCurrentURL();
            ...
        }
    };
    // Test
    var urlSpy = spyOn(actions, "getCurrentURL").andReturn("http://my/fake?param");
    expect(actions.paramToVar(test_Data)).toEqual("bar");
    
  • Utilisez une injection dependency et injectez un faux dans votre test:

    var _actions = function (window) {
        return {
            paramToVar: function (testData) {
                ...
                var url = window.location.href;
                ...
            }
        };
    };
    var actions = _actions(window);
    // Test
    var fakeWindow = {
       location: { href: "http://my/fake?param" }
    };
    var fakeActions = _actions(fakeWindow);
    expect(fakeActions.paramToVar(test_Data)).toEqual("bar");
    
17
cburgmer

Parfois, vous pouvez avoir une bibliothèque qui modifie window.location et vous voulez lui permettre de fonctionner normalement mais également d’être testée. Si tel est le cas, vous pouvez utiliser une fermeture pour transmettre la référence souhaitée à votre bibliothèque, telle que celle-ci.

/* in mylib.js */
(function(view){
    view.location.href = "foo";
}(self || window));

Ensuite, dans votre test, avant d'inclure votre bibliothèque, vous pouvez redéfinir l'auto globalement, et la bibliothèque utilisera l'auto-maquette comme vue.

var self = {
   location: { href: location.href }
};

Dans votre bibliothèque, vous pouvez également faire quelque chose comme ce qui suit pour vous permettre de vous redéfinir à tout moment du test:

/* in mylib.js */
var mylib = (function(href) {
    function go ( href ) {
       var view = self || window;
       view.location.href = href;
    }
    return {go: go}
}());

Dans la plupart des navigateurs modernes, sinon tous, self est déjà une référence à window par défaut. Sur les plates-formes qui implémentent l'API de Worker, Worker self est une référence à la portée globale. Dans node.js, self et window ne sont pas définis. Si vous le souhaitez, vous pouvez également le faire:

self || window || global

Cela peut changer si node.js implémente vraiment l'API Worker.

5
Jim Isaacs

Vous trouverez ci-dessous l'approche que j'ai adoptée pour simuler window.location.href et/ou tout autre élément susceptible de s'appliquer à un objet global.

Tout d'abord, plutôt que d'y accéder directement, encapsulez-le dans un module où l'objet est conservé avec un getter et un setter. Voici mon exemple. J'utilise besoin, mais ce n'est pas nécessaire ici.

define(["exports"], function(exports){

  var win = window;

  exports.getWindow = function(){
    return win;
  };

  exports.setWindow = function(x){
    win = x;
  }

});

Maintenant, là où vous avez normalement fait dans votre code quelque chose comme window.location.href, maintenant vous feriez quelque chose comme:

var window = global_window.getWindow();
var hrefString = window.location.href;

Enfin, la configuration est terminée et vous pouvez tester votre code en remplaçant l'objet window par un objet fictif que vous souhaitez remplacer. 

fakeWindow = {
  location: {
    href: "http://google.com?x=y"
  }
}
w = require("helpers/global_window");
w.setWindow(fakeWindow);

Cela modifierait la variable win dans le module de fenêtre. A l'origine, il était défini sur l'objet global window, mais pas sur l'objet fausse fenêtre que vous avez inséré. Ainsi, après l'avoir remplacé, le code obtiendra votre objet fausse fenêtre et son faux href. 

3
user566245

OMI, cette solution est une petite amélioration de cburgmer en ce sens qu'elle vous permet de remplacer window.location.href par $ window.location.href dans le source. Certes, j'utilise Karma et pas Jasmine, mais je crois que cette approche fonctionnerait avec l'un ou l'autre. Et j'ai ajouté une dépendance sur sinon.

D'abord un service/singleton:

function setHref(theHref) {
    window.location.href = theHref;
}
function getHref(theHref) {
    return window.location.href;
}

var $$window = {
        location: {
            setHref: setHref,
            getHref: getHref,
            get href() {
                return this.getHref();
            },
            set href(v) {
                this.setHref(v);
            }
        }
    };
function windowInjectable() {  return $$window; }

Maintenant, je peux définir location.href dans le code en injectant windowInjectable () en tant que $ window comme ceci:

function($window) {
  $window.location.href = "http://www.website.com?varName=foo";
}

et se moquer dans un test unitaire, il ressemble à:

sinon.stub($window.location, 'setHref');  // this prevents the true window.location.href from being hit.
expect($window.location.setHref.args[0][0]).to.contain('varName=foo');
$window.location.setHref.restore();

La syntaxe getter/setter remonte à IE 9 et est par ailleurs largement supportée selon https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set

0
Tongfa