¿Cómo trato con localStorage en las pruebas de broma?

144

Sigo recibiendo "localStorage no está definido" en las pruebas de Jest, lo que tiene sentido, pero ¿cuáles son mis opciones? Golpear paredes de ladrillo.

Chiedo
fuente

Respuestas:

141

Gran solución de @chiedo

Sin embargo, usamos la sintaxis ES2015 y sentí que era un poco más limpio escribirlo de esta manera.

class LocalStorageMock {
  constructor() {
    this.store = {};
  }

  clear() {
    this.store = {};
  }

  getItem(key) {
    return this.store[key] || null;
  }

  setItem(key, value) {
    this.store[key] = value.toString();
  }

  removeItem(key) {
    delete this.store[key];
  }
};

global.localStorage = new LocalStorageMock;
nickcan
fuente
8
Probablemente debería hacer value + ''en el setter para manejar correctamente los valores nulos e indefinidos
menehune23
Creo que la última broma solo estaba usando eso || null, por eso mi prueba estaba fallando, porque en mi prueba estaba usando not.toBeDefined(). La solución @Chiedo lo hace funcionar de nuevo
jcubic
Creo que esto es técnicamente un trozo :) vea aquí la versión
simulada
100

Lo descubrí con ayuda de esto: https://groups.google.com/forum/#!topic/jestjs/9EPhuNWVYTg

Configure un archivo con los siguientes contenidos:

var localStorageMock = (function() {
  var store = {};
  return {
    getItem: function(key) {
      return store[key];
    },
    setItem: function(key, value) {
      store[key] = value.toString();
    },
    clear: function() {
      store = {};
    },
    removeItem: function(key) {
      delete store[key];
    }
  };
})();
Object.defineProperty(window, 'localStorage', { value: localStorageMock });

Luego agrega la siguiente línea a su package.json en sus configuraciones de Jest

"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",

Chiedo
fuente
66
Aparentemente, con una de las actualizaciones, el nombre de este parámetro cambió y ahora se llama "setupTestFrameworkScriptFile"
Grzegorz Pawlik
2
"setupFiles": [...]funciona igual de bien Con la opción de matriz, permite separar simulacros en archivos separados. Por ejemplo:"setupFiles": ["<rootDir>/__mocks__/localStorageMock.js"]
Stiggler
3
El valor de retorno de getItemdifiere ligeramente de lo que devolvería un navegador si los datos no se configuran con una clave específica. llamar getItem("foo")cuando no está configurado, por ejemplo, volverá nullen un navegador, pero undefinedcon este simulacro, esto estaba causando que fallara una de mis pruebas. La solución simple para mí fue regresar store[key] || nulla la getItemfunción
Ben Broadley,
esto no funciona si haces algo comolocalStorage['test'] = '123'; localStorage.getItem('test')
robar el
3
Recibo el siguiente error: el valor de jest.fn () debe ser una función simulada o espía. ¿Algunas ideas?
Paul Fitzgerald
55

Si usa create-react-app, hay una solución más simple y directa explicada en la documentación .

Crea src/setupTests.jsy pon esto en él:

const localStorageMock = {
  getItem: jest.fn(),
  setItem: jest.fn(),
  clear: jest.fn()
};
global.localStorage = localStorageMock;

Contribución de Tom Mertz en un comentario a continuación:

Luego puede probar que las funciones de su LocalStorageMock se usan haciendo algo como

expect(localStorage.getItem).toBeCalledWith('token')
// or
expect(localStorage.getItem.mock.calls.length).toBe(1)

dentro de sus pruebas si quería asegurarse de que se llamara. Echa un vistazo a https://facebook.github.io/jest/docs/en/mock-functions.html

c4k
fuente
Hola c4k ¿Podría dar un ejemplo de cómo usaría eso en sus pruebas?
Dimo
Qué quieres decir ? No tiene que inicializar nada en sus pruebas, solo se burla automáticamente de lo localStorageque usa en su código. (si usa create-react-appy todos los scripts automáticos que proporciona naturalmente)
c4k
Luego puede probar que las funciones de su LocalStorageMock se usan haciendo algo como expect(localStorage.getItem).toBeCalledWith('token')o expect(localStorage.getItem.mock.calls.length).toBe(1)dentro de sus pruebas si desea asegurarse de que se haya llamado. Echa un vistazo a facebook.github.io/jest/docs/en/mock-functions.html
Tom Mertz
10
para esto recibo un error: el valor de jest.fn () debe ser una función simulada o espía. ¿Algunas ideas?
Paul Fitzgerald
3
¿Esto no causará problemas si tiene múltiples pruebas que usan localStorage? ¿No le gustaría reiniciar los espías después de cada prueba para evitar el "derrame" en otras pruebas?
Brandon Sturgeon el
43

Actualmente (Oct '19) localStorage no puede ser burlado o espiado por broma como lo haría normalmente, y como se describe en los documentos de create-react-app. Esto se debe a los cambios realizados en jsdom. Puedes leer sobre esto en broma y jsdom rastreadores de problemas de .

Como solución alternativa, puede espiar el prototipo en su lugar:

// does not work:
jest.spyOn(localStorage, "setItem");
localStorage.setItem = jest.fn();

// works:
jest.spyOn(window.localStorage.__proto__, 'setItem');
window.localStorage.__proto__.setItem = jest.fn();

// assertions as usual:
expect(localStorage.setItem).toHaveBeenCalled();
Bastian Stein
fuente
En realidad, funciona para mí solo con el spyOn, no es necesario anular la función setItemjest.spyOn(window.localStorage.__proto__, 'setItem');
Yohan Dahmani
Sí, enumeré las dos como alternativas, no es necesario hacer ambas.
Bastian Stein
quise decir sin anular el setItem también 😉
Yohan Dahmani
No creo entender. ¿Puedes aclarar por favor?
Bastian Stein
1
Ah, sí. Estaba diciendo que puedes usar la primera línea o la segunda línea. Son alternativas que hacen lo mismo. Cualquiera sea su preferencia personal :) Perdón por la confusión.
Bastian Stein
13

Una mejor alternativa que maneja undefinedvalores (no tiene toString()) y devuelve nullsi el valor no existe. reactProbé esto con v15, reduxyredux-auth-wrapper

class LocalStorageMock {
  constructor() {
    this.store = {}
  }

  clear() {
    this.store = {}
  }

  getItem(key) {
    return this.store[key] || null
  }

  setItem(key, value) {
    this.store[key] = value
  }

  removeItem(key) {
    delete this.store[key]
  }
}

global.localStorage = new LocalStorageMock
Dmitriy
fuente
Gracias a Alexis Tyler por la idea de agregar removeItem: developer.mozilla.org/en-US/docs/Web/API/Storage/removeItem
Dmitriy
Creer nula y necesidad definida para dar lugar a "nulo" y "indefinido" (cadenas literales)
menehune23
6

Si está buscando un simulacro y no un trozo, aquí está la solución que uso:

export const localStorageMock = {
   getItem: jest.fn().mockImplementation(key => localStorageItems[key]),
   setItem: jest.fn().mockImplementation((key, value) => {
       localStorageItems[key] = value;
   }),
   clear: jest.fn().mockImplementation(() => {
       localStorageItems = {};
   }),
   removeItem: jest.fn().mockImplementation((key) => {
       localStorageItems[key] = undefined;
   }),
};

export let localStorageItems = {}; // eslint-disable-line import/no-mutable-exports

Exporto los elementos de almacenamiento para una fácil inicialización. IE puedo configurarlo fácilmente en un objeto

En las versiones más recientes de Jest + JSDom no es posible configurar esto, pero el almacenamiento local ya está disponible y puede espiarlo de esta manera:

const setItemSpy = jest.spyOn(Object.getPrototypeOf(window.localStorage), 'setItem');
Tigre
fuente
5

Encontré esta solución de github

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

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

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

Puede insertar este código en sus SetupTests y debería funcionar bien.

Lo probé en un proyecto con typectipt.

Carlos Huamani
fuente
para mí Object.defineProperty hizo el truco. La asignación directa de objetos no funcionó. ¡Gracias!
Vicens Fayos
4

Desafortunadamente, las soluciones que he encontrado aquí no funcionaron para mí.

Así que estaba mirando los problemas de Jest GitHub y encontré este hilo

Las soluciones más votadas fueron estas:

const spy = jest.spyOn(Storage.prototype, 'setItem');

// or

Storage.prototype.getItem = jest.fn(() => 'bla');
Christian Saiki
fuente
Mis pruebas tampoco tienen windowni están Storagedefinidas. Tal vez es la versión anterior de Jest que estoy usando.
Antrikshy
3

Como @ ck4 la documentación sugerida tiene una explicación clara para usar localStorageen broma. Sin embargo, las funciones simuladas no podían ejecutar ninguna de laslocalStorage métodos.

A continuación se muestra el ejemplo detallado de mi componente de reacción que utiliza métodos abstractos para escribir y leer datos,

//file: storage.js
const key = 'ABC';
export function readFromStore (){
    return JSON.parse(localStorage.getItem(key));
}
export function saveToStore (value) {
    localStorage.setItem(key, JSON.stringify(value));
}

export default { readFromStore, saveToStore };

Error:

TypeError: _setupLocalStorage2.default.setItem is not a function

Solución:
Añadir a continuación la función simulacro de broma (ruta: .jest/mocks/setUpStore.js)

let mockStorage = {};

module.exports = window.localStorage = {
  setItem: (key, val) => Object.assign(mockStorage, {[key]: val}),
  getItem: (key) => mockStorage[key],
  clear: () => mockStorage = {}
};

Se hace referencia al fragmento desde aquí

Mad-D
fuente
2

Riffed algunas otras respuestas aquí para resolverlo para un proyecto con Typecript. Creé un LocalStorageMock como este:

export class LocalStorageMock {

    private store = {}

    clear() {
        this.store = {}
    }

    getItem(key: string) {
        return this.store[key] || null
    }

    setItem(key: string, value: string) {
        this.store[key] = value
    }

    removeItem(key: string) {
        delete this.store[key]
    }
}

Luego creé una clase LocalStorageWrapper que uso para todo el acceso al almacenamiento local en la aplicación en lugar de acceder directamente a la variable de almacenamiento local global. Facilitó la configuración del simulacro en el contenedor para las pruebas.

CorayThan
fuente
2
    describe('getToken', () => {
    const Auth = new AuthService();
    const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Ik1yIEpvc2VwaCIsImlkIjoiNWQwYjk1Mzg2NTVhOTQ0ZjA0NjE5ZTA5IiwiZW1haWwiOiJ0cmV2X2pvc0Bob3RtYWlsLmNvbSIsInByb2ZpbGVVc2VybmFtZSI6Ii9tcmpvc2VwaCIsInByb2ZpbGVJbWFnZSI6Ii9Eb3Nlbi10LUdpci1sb29rLWN1dGUtbnVrZWNhdDMxNnMtMzExNzAwNDYtMTI4MC04MDAuanBnIiwiaWF0IjoxNTYyMzE4NDA0LCJleHAiOjE1OTM4NzYwMDR9.YwU15SqHMh1nO51eSa0YsOK-YLlaCx6ijceOKhZfQZc';
    beforeEach(() => {
        global.localStorage = jest.fn().mockImplementation(() => {
            return {
                getItem: jest.fn().mockReturnValue(token)
            }
        });
    });
    it('should get the token from localStorage', () => {

        const result  = Auth.getToken();
        expect(result).toEqual(token);

    });
});

Crea un simulacro y globalagrégalo al objeto

Trevor Joseph
fuente
2

Puedes usar este enfoque para evitar burlarte.

Storage.prototype.getItem = jest.fn(() => expectedPayload);
Sanath
fuente
2

Debes burlarte del almacenamiento local con estos fragmentos

// localStorage.js

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

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

})();

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

Y en la configuración de broma:

"setupFiles":["localStorage.js"]

Siéntase libre de preguntar cualquier cosa.

Codificador delgado
fuente
1

La siguiente solución es compatible para probar con configuraciones TypeScript, ESLint, TSLint y Prettier más estrictas { "proseWrap": "always", "semi": false, "singleQuote": true, "trailingComma": "es5" }:

class LocalStorageMock {
  public store: {
    [key: string]: string
  }
  constructor() {
    this.store = {}
  }

  public clear() {
    this.store = {}
  }

  public getItem(key: string) {
    return this.store[key] || undefined
  }

  public setItem(key: string, value: string) {
    this.store[key] = value.toString()
  }

  public removeItem(key: string) {
    delete this.store[key]
  }
}
/* tslint:disable-next-line:no-any */
;(global as any).localStorage = new LocalStorageMock()

HT / https://stackoverflow.com/a/51583401/101290 sobre cómo actualizar global.localStorage

Beau Smith
fuente
1

Para hacer lo mismo en el mecanografiado, haga lo siguiente:

Configure un archivo con los siguientes contenidos:

let localStorageMock = (function() {
  let store = new Map()
  return {

    getItem(key: string):string {
      return store.get(key);
    },

    setItem: function(key: string, value: string) {
      store.set(key, value);
    },

    clear: function() {
      store = new Map();
    },

    removeItem: function(key: string) {
        store.delete(key)
    }
  };
})();
Object.defineProperty(window, 'localStorage', { value: localStorageMock });

Luego agrega la siguiente línea a su package.json en sus configuraciones de Jest

"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",

O importa este archivo en su caso de prueba donde desea burlarse del almacenamiento local.

vs_lala
fuente
0

Esto funcionó para mí

delete global.localStorage;
global.localStorage = {
getItem: () => 
 }
prathic
fuente