variable setitem read only localstorage example create javascript unit-testing mocking local-storage sinon

setitem - localstorage var javascript



¿Cómo burlarse localStorage en las pruebas de unidades de JavaScript? (11)

¿Hay alguna biblioteca para burlarse de localStorage ?

Acabo de escribir uno:

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

Mi prueba inicial muestra que localStorage se niega a ser asignable en Firefox

Solo en el contexto global. Con una función de envoltura como la anterior, funciona bien.

¿Hay alguna biblioteca para burlarse de localStorage ?

He estado usando Sinon.JS para la mayoría de mis otras burlas de JavaScript y he descubierto que es realmente genial.

Mi prueba inicial muestra que localStorage se niega a ser asignable en firefox (sadface) así que probablemente necesite algún tipo de truco sobre esto: /

Mis opciones a partir de ahora (como veo) son las siguientes:

  1. Crear funciones de ajuste que todo mi código usa y simular esas
  2. Cree algún tipo de administración de estado (podría ser complicado) (instantánea localStorage antes de la prueba, en la restauración instantánea de restauración) para localStorage.
  3. ??????

¿Qué piensas de estos enfoques y crees que hay otras formas mejores de hacerlo? De cualquier manera, pondré la "biblioteca" resultante que termino haciendo en github por bondad de fuente abierta.


Aquí hay un ejemplo usando sinon espía y simulacro:

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


Aquí hay una manera simple de burlarse de ella con Jasmine:

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

Si desea simular el almacenamiento local en todas sus pruebas, declare la función beforeEach() que se muestra arriba en el alcance global de sus pruebas (el lugar habitual es un script specHelper.js ).


Así es como me gusta hacerlo. Lo mantiene simple.

let localStoreMock: any = {}; beforeEach(() => { angular.mock.module(''yourApp''); angular.mock.module(function ($provide: any) { $provide.service(''localStorageService'', function () { this.get = (key: any) => localStoreMock[key]; this.set = (key: any, value: any) => localStoreMock[key] = value; }); }); });


Decidí reiterar mi comentario a la respuesta de Pumbaa80 como respuesta por separado para que sea más fácil reutilizarlo como una biblioteca.

Tomé el código de Pumbaa80, lo refiné un poco, agregué pruebas y lo publiqué como un módulo npm aquí: https://www.npmjs.com/package/mock-local-storage .

Aquí hay un código fuente: https://github.com/letsrock-today/mock-local-storage/blob/master/src/mock-localstorage.js

Algunas pruebas: https://github.com/letsrock-today/mock-local-storage/blob/master/test/mock-localstorage.js

El módulo crea localStorage falso y sessionStorage en el objeto global (ventana o global, cuál de ellos está definido).

En las pruebas de mi otro proyecto lo requería con mocha como este: mocha -r mock-local-storage para hacer que las definiciones globales estén disponibles para todo el código bajo prueba.

Básicamente, el código se ve de la siguiente manera:

(function (glob) { function createStorage() { let s = {}, noopCallback = () => {}, _itemInsertionCallback = noopCallback; Object.defineProperty(s, ''setItem'', { get: () => { return (k, v) => { k = k + ''''; _itemInsertionCallback(s.length); s[k] = v + ''''; }; } }); Object.defineProperty(s, ''getItem'', { // ... }); Object.defineProperty(s, ''removeItem'', { // ... }); Object.defineProperty(s, ''clear'', { // ... }); Object.defineProperty(s, ''length'', { get: () => { return Object.keys(s).length; } }); Object.defineProperty(s, "key", { // ... }); Object.defineProperty(s, ''itemInsertionCallback'', { get: () => { return _itemInsertionCallback; }, set: v => { if (!v || typeof v != ''function'') { v = noopCallback; } _itemInsertionCallback = v; } }); return s; } glob.localStorage = createStorage(); glob.sessionStorage = createStorage(); }(typeof window !== ''undefined'' ? window : global));

Tenga en cuenta que todos los métodos se agregan a través de Object.defineProperty para que no se Object.defineProperty o eliminen como elementos regulares y no contarán en longitud. También agregué una forma de registrar la devolución de llamada que se llama cuando un elemento está a punto de ser puesto en el objeto. Esta devolución de llamada se puede usar para emular el error de exceso de cuota en las pruebas.


Desafortunadamente, la única forma en que podemos simular el objeto localStorage en un escenario de prueba es cambiar el código que estamos probando. Debe envolver su código en una función anónima (que debería estar haciendo de todos modos) y usar "inyección de dependencia" para pasar una referencia al objeto ventana. Algo como:

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

Luego, dentro de su prueba, puede especificar:

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


Esto es lo que hago...

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


No tiene que pasar el objeto de almacenamiento a cada método que lo usa. En su lugar, puede usar un parámetro de configuración para cualquier módulo que toque el adaptador de almacenamiento.

Tu antiguo módulo

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

Su nuevo módulo con la función config "wrapper"

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

Cuando usas el módulo en el código de prueba

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

La clase MockStorage podría verse así

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

Cuando use su módulo en el código de producción, en su lugar pase el adaptador de almacenamiento local real

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


Sobrescribir la propiedad localStorage del objeto de window global como se sugiere en algunas de las respuestas no funcionará en la mayoría de los motores JS, ya que declaran la propiedad de datos localStorage como no modificable y no configurable.

Sin embargo, descubrí que, al menos con la versión WebKit de PhantomJS (versión 1.9.8), podría usar la API heredada __defineGetter__ para controlar lo que sucede si se accede a localStorage . Aún así sería interesante si esto funciona en otros navegadores también.

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

El beneficio de este enfoque es que no tendría que modificar el código que está a punto de probar.


También considere la opción de inyectar dependencias en la función de constructor de un objeto.

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 línea con la burla y las pruebas unitarias, me gusta evitar probar la implementación de almacenamiento. Por ejemplo, no tiene sentido verificar si la duración del almacenamiento aumentó después de configurar un elemento, etc.

Dado que obviamente no es confiable reemplazar los métodos en el objeto localStorage real, use un archivo simulado "mudo" y resuelva los métodos individuales como desee, como por ejemplo:

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


simplemente simule el localStorage / sessionStorage global (tienen la misma API) para sus necesidades.
Por ejemplo:

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

Y luego lo que realmente haces, es algo como eso:

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