Frage Wie spottet localStorage in JavaScript-Unit-Tests?


Gibt es da draußen irgendwelche Bibliotheken, die sich verspotten? localStorage?

Ich habe benutzt Sinon.JS für die meisten meiner anderen Javascript-Spott und habe festgestellt, dass es wirklich toll ist.

Meine ersten Tests zeigen, dass localStorage in Firefox (sadface) nicht zuweisbar ist, also brauche ich wahrscheinlich eine Art Hack:

Meine Optionen ab jetzt (wie ich sehe) sind wie folgt:

  1. Erstellen Sie Wrapping-Funktionen, die alle meinen Code verwendet und diese vortäuschen
  2. Erstellen Sie eine Art von (möglicherweise komplizierter) Statusverwaltung (Snapshot localStorage vor dem Test, im Bereinigungswiederherstellungs-Snapshot) für localStorage.
  3. ??????

Was denkst du über diese Ansätze und denkst du, es gibt andere bessere Möglichkeiten, dies zu tun? So oder so, ich werde die resultierende "Bibliothek", die ich am Ende auf Github für Open-Source-Güte machen.


75
2017-07-14 16:24


Ursprung


Antworten:


Hier ist eine einfache Art, es mit Jasmine zu verspotten:

beforeEach(function () {
  var store = {};

  spyOn(localStorage, 'getItem').andCallFake(function (key) {
    return store[key];
  });
  spyOn(localStorage, 'setItem').andCallFake(function (key, value) {
    return store[key] = value + '';
  });
  spyOn(localStorage, 'clear').andCallFake(function () {
      store = {};
  });
});

Wenn Sie den lokalen Speicher in all Ihren Tests verspotten möchten, deklarieren Sie die beforeEach() Funktion, die oben im globalen Umfang Ihrer Tests gezeigt wird (der übliche Ort ist a specHelper.js Skript).


102
2018-01-17 15:04



Spotten Sie einfach die globale localStorage / sessionStorage (sie haben die gleiche API) für Ihre Bedürfnisse.
Beispielsweise:

 // Storage Mock
  function storageMock() {
    var 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) {
        var keys = Object.keys(storage);
        return keys[i] || null;
      }
    };
  }

Und dann, was Sie tatsächlich tun, ist so etwas:

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

38
2017-10-03 11:07



Beachten Sie auch die Option, Abhängigkeiten in die Konstruktorfunktion eines Objekts einzufügen.

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)

In Übereinstimmung mit Mocking und Unit-Tests vermeide ich es, die Storage-Implementierung zu testen. Zum Beispiel kein Punkt in der Überprüfung, ob die Länge des Speichers erhöht wird, nachdem Sie einen Artikel usw. gesetzt haben.

Da es offensichtlich unzuverlässig ist, Methoden auf dem realen localStorage-Objekt zu ersetzen, verwenden Sie eine "dumme" mockStorage und stub die einzelnen Methoden wie gewünscht, wie zum Beispiel:

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

17
2017-11-22 20:04



Gibt es da draußen irgendwelche Bibliotheken, die sich verspotten? localStorage?

Ich habe gerade einen geschrieben:

(function () {
    var localStorage = {};
    localStorage.setItem = function (key, val) {
         this[key] = val + '';
    }
    localStorage.getItem = function (key) {
        return this[key];
    }
    Object.defineProperty(localStorage, 'length', {
        get: function () { return Object.keys(this).length - 2; }
    });

    // Your tests here

})();

Meine ersten Tests zeigen, dass localStorage in Firefox nicht zuweisbar ist

Nur im globalen Kontext. Mit einer Wrapper-Funktion wie oben funktioniert es gut.


7
2017-07-14 18:34



Das ist was ich mache...

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

6
2018-03-03 10:11



Hier ist ein Beispiel mit sinon spy und mock:

// window.localStorage.setItem
var spy = sinon.spy(window.localStorage, "setItem");

// You can use this in your assertions
spy.calledWith(aKey, aValue)

// Reset localStorage.setItem method    
spy.reset();



// window.localStorage.getItem
var stub = sinon.stub(window.localStorage, "getItem");
stub.returns(aValue);

// You can use this in your assertions
stub.calledWith(aKey)

// Reset localStorage.getItem method
stub.reset();

4
2018-03-02 14:12



Sie müssen das Speicherobjekt nicht an jede Methode übergeben, die es verwendet. Stattdessen können Sie einen Konfigurationsparameter für jedes Modul verwenden, das den Speicheradapter berührt.

Dein altes Modul

// hard to test !
export const someFunction (x) {
  window.localStorage.setItem('foo', x)
}

// hard to test !
export const anotherFunction () {
  return window.localStorage.getItem('foo')
}

Ihr neues Modul mit Config "Wrapper" -Funktion

export default function (storage) {
  return {
    someFunction (x) {
      storage.setItem('foo', x)
    }
    anotherFunction () {
      storage.getItem('foo')
    }
  }
}

Wenn Sie das Modul zum Testen von Code verwenden

// import mock storage adapater
const MockStorage = require('./mock-storage')

// create a new mock storage instance
const mock = new MockStorage()

// pass mock storage instance as configuration argument to your module
const myModule = require('./my-module')(mock)

// reset before each test
beforeEach(function() {
  mock.clear()
})

// your tests
it('should set foo', function() {
  myModule.someFunction('bar')
  assert.equal(mock.getItem('foo'), 'bar')
})

it('should get foo', function() {
  mock.setItem('foo', 'bar')
  assert.equal(myModule.anotherFunction(), 'bar')
})

Das MockStorage Klasse könnte so aussehen

export default class MockStorage {
  constructor () {
    this.storage = new Map()
  }
  setItem (key, value) {
    this.storage.set(key, value)
  }
  getItem (key) {
    return this.storage.get(key)
  }
  removeItem (key) {
    this.storage.delete(key)
  }
  clear () {
    this.constructor()
  }
}

Wenn Sie Ihr Modul im Produktionscode verwenden, übergeben Sie stattdessen den echten localStorage-Adapter

const myModule = require('./my-module')(window.localStorage)

4
2017-11-07 19:03



Überschreiben der localStorage Eigenschaft des Globalen window Objekt, wie es in einigen der Antworten vorgeschlagen wird, wird in den meisten JS-Engines nicht funktionieren, weil sie das deklarieren localStorage Dateneigenschaft als nicht beschreibbar und nicht konfigurierbar.

Allerdings habe ich festgestellt, dass zumindest mit der WebKit-Version von PhantomJS (Version 1.9.8) die Legacy-API verwendet werden kann __defineGetter__ zu kontrollieren, was passiert, wenn localStorage wird zugegriffen. Dennoch wäre es interessant, wenn dies auch in anderen Browsern funktioniert.

var tmpStorage = window.localStorage;

// replace local storage
window.__defineGetter__('localStorage', function () {
    throw new Error("localStorage not available");
    // you could also return some other object here as a mock
});

// do your tests here    

// restore old getter to actual local storage
window.__defineGetter__('localStorage',
                        function () { return tmpStorage });

Der Vorteil dieses Ansatzes besteht darin, dass Sie den Code, den Sie testen möchten, nicht ändern müssen.


2
2018-02-10 15:18



Leider können wir das LocalStorage-Objekt in einem Testszenario nur verspotten, indem wir den Code, den wir testen, ändern. Sie müssen Ihren Code in eine anonyme Funktion umbrechen (was Sie ohnehin tun sollten) und "Abhängigkeitsinjektion" verwenden, um einen Verweis auf das Fensterobjekt zu übergeben. Etwas wie:

(function (window) {
   // Your code
}(window.mockWindow || window));

Innerhalb Ihres Tests können Sie dann Folgendes angeben:

window.mockWindow = { localStorage: { ... } };

1
2018-03-06 21:30