144 votes

Comment simuler localStorage dans les tests unitaires JavaScript ?

Existe-t-il des bibliothèques pour se moquer localStorage ?

J'ai utilisé Sinon.JS pour la plupart de mes autres simulations en javascript et j'ai trouvé que c'était vraiment génial.

Mes premiers tests montrent que localStorage refuse d'être assignable dans firefox (sadface) donc je vais probablement avoir besoin d'une sorte de hack pour contourner cela :/.

Mes options à l'heure actuelle (telles que je les vois) sont les suivantes :

  1. Créer des fonctions d'habillage que tout mon code utilise et les simuler.
  2. Créez une sorte de gestion d'état (qui pourrait être compliquée) (instantané de localStorage avant le test, restauration de l'instantané lors du nettoyage) pour localStorage.
  3. ??????

Que pensez-vous de ces approches et pensez-vous qu'il existe d'autres meilleures façons de procéder ? Quoi qu'il en soit, je mettrai la "bibliothèque" que je finirai par créer sur github pour qu'elle puisse être utilisée en open source.

168voto

Andreas Köberle Points 16453

Voici une façon simple de le simuler avec Jasmine :

let localStore;

beforeEach(() => {
  localStore = {};

  spyOn(window.localStorage, 'getItem').and.callFake((key) =>
    key in localStore ? localStore[key] : null
  );
  spyOn(window.localStorage, 'setItem').and.callFake(
    (key, value) => (localStore[key] = value + '')
  );
  spyOn(window.localStorage, 'clear').and.callFake(() => (localStore = {}));
});

Si vous voulez simuler le stockage local dans tous vos tests, déclarez l'attribut beforeEach() montrée ci-dessus dans la portée globale de vos tests (l'endroit habituel est le fichier specHelper.js script).

60voto

Ariel M. Points 141

Il suffit de simuler le localStorage / sessionStorage global (ils ont la même API) pour vos besoins.
Par exemple :

 // Storage Mock
  function storageMock() {
    let storage = {};

    return {
      setItem: function(key, value) {
        storage[key] = value || '';
      },
      getItem: function(key) {
        return key in storage ? storage[key] : null;
      },
      removeItem: function(key) {
        delete storage[key];
      },
      get length() {
        return Object.keys(storage).length;
      },
      key: function(i) {
        const keys = Object.keys(storage);
        return keys[i] || null;
      }
    };
  }

Et puis ce que vous faites réellement, c'est quelque chose comme ça :

// mock the localStorage
window.localStorage = storageMock();
// mock the sessionStorage
window.sessionStorage = storageMock();

32voto

Eru Penkman Points 1635

Les solutions actuelles ne fonctionneront pas dans Firefox. Cela est dû au fait que localStorage est défini par la spécification html comme n'étant pas modifiable. Vous pouvez toutefois contourner ce problème en accédant directement au prototype de localStorage.

La solution pour les navigateurs croisés est de simuler les objets sur Storage.prototype par exemple

au lieu de spyOn(localStorage, 'setItem') utiliser

spyOn(Storage.prototype, 'setItem')
spyOn(Storage.prototype, 'getItem')

tiré de bzbarsky y teogeos Réponses de l'auteur ici https://github.com/jasmine/jasmine/issues/299

23voto

Claudijo Points 252

Pensez également à la possibilité d'injecter des dépendances dans la fonction constructeur d'un objet.

var SomeObject(storage) {
  this.storge = storage || window.localStorage;
  // ...
}

SomeObject.prototype.doSomeStorageRelatedStuff = function() {
  var myValue = this.storage.getItem('myKey');
  // ...
}

// In src
var myObj = new SomeObject();

// In test
var myObj = new SomeObject(mockStorage)

En accord avec le mocking et les tests unitaires, j'aime éviter de tester l'implémentation du stockage. Par exemple, il ne sert à rien de vérifier si la longueur du stockage a augmenté après avoir défini un élément, etc.

Étant donné qu'il n'est manifestement pas fiable de remplacer les méthodes sur le véritable objet localStorage, utilisez un mockStorage "muet" et remplacez les méthodes individuelles comme vous le souhaitez, par exemple :

var mockStorage = {
  setItem: function() {},
  removeItem: function() {},
  key: function() {},
  getItem: function() {},
  removeItem: function() {},
  length: 0
};

// Then in test that needs to know if and how setItem was called
sinon.stub(mockStorage, 'setItem');
var myObj = new SomeObject(mockStorage);

myObj.doSomeStorageRelatedStuff();
expect(mockStorage.setItem).toHaveBeenCalledWith('myKey');

14voto

CharlesJHardy Points 2701

C'est ce que je fais...

var mock = (function() {
  var store = {};
  return {
    getItem: function(key) {
      return store[key];
    },
    setItem: function(key, value) {
      store[key] = value.toString();
    },
    clear: function() {
      store = {};
    }
  };
})();

Object.defineProperty(window, 'localStorage', { 
  value: mock,
});

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X