Come gestisco localStorage nei test jest?


144

Continuo a ricevere "localStorage non definito" nei test Jest che ha senso ma quali sono le mie opzioni? Colpire muri di mattoni.

Risposte:


141

Ottima soluzione di @chiedo

Tuttavia, usiamo la sintassi ES2015 e ho pensato che fosse un po 'più pulito scriverlo in questo modo.

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;

8
Probabilmente dovrebbe farlo value + ''nel setter per gestire correttamente i valori null e indefiniti
menehune23

Penso che l'ultimo scherzo stia solo usando questo è || nullil motivo per cui il mio test falliva, perché nel mio test stavo usando not.toBeDefined(). La soluzione di @Chiedo lo fa funzionare di nuovo
jcubic

Credo che questo sia tecnicamente uno stub :) vedo qui per la versione deriso: stackoverflow.com/questions/32911630/...
TigerBear

100

Capito con l'aiuto di questo: https://groups.google.com/forum/#!topic/jestjs/9EPhuNWVYTg

Imposta un file con i seguenti contenuti:

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

Quindi aggiungi la seguente riga a package.json sotto le tue configurazioni Jest

"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",


6
Apparentemente con uno degli aggiornamenti il ​​nome di questo parametro è cambiato e ora si chiama "setupTestFrameworkScriptFile"
Grzegorz Pawlik

2
"setupFiles": [...]funziona pure. Con l'opzione array, consente di separare i mock in file separati. Ad esempio:"setupFiles": ["<rootDir>/__mocks__/localStorageMock.js"]
Stiggler

3
Il valore di ritorno getItemdifferisce leggermente da ciò che verrebbe restituito da un browser se nessun dato viene impostato su una chiave specifica. chiamare getItem("foo")quando non è impostato, ad esempio, tornerà nullin un browser, ma undefinedcon questo finto - questo stava causando il fallimento di uno dei miei test. La soluzione semplice per me era tornare store[key] || nullalla getItemfunzione
Ben Broadley,

questo non funziona se fai qualcosa del generelocalStorage['test'] = '123'; localStorage.getItem('test')
ruba il

3
Ricevo il seguente errore: il valore jest.fn () deve essere una funzione o spia spia. Qualche idea?
Paul Fitzgerald,

55

Se si utilizza create -eagire-app, esiste una soluzione più semplice e chiara spiegata nella documentazione .

Crea src/setupTests.jse inseriscilo:

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

Contributo di Tom Mertz in un commento qui sotto:

È quindi possibile verificare che le funzioni di localStorageMock vengano utilizzate facendo qualcosa di simile

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

all'interno dei tuoi test se volevi assicurarti che fosse chiamato. Dai un'occhiata a https://facebook.github.io/jest/docs/en/mock-functions.html


Ciao c4k! Potresti per favore fare un esempio di come lo useresti nei tuoi test?
Dimo,

Cosa intendi ? Non devi inizializzare nulla nei tuoi test, prende semplicemente in giro automaticamente ciò localStorageche usi nel tuo codice. (se usi create-react-appe tutti gli script automatici che fornisce naturalmente)
c4k,

È quindi possibile verificare che le funzioni di localStorageMock vengano utilizzate eseguendo qualcosa di simile expect(localStorage.getItem).toBeCalledWith('token')o expect(localStorage.getItem.mock.calls.length).toBe(1)all'interno dei test se si desidera assicurarsi che sia stato chiamato. Dai un'occhiata a facebook.github.io/jest/docs/en/mock-functions.html
Tom Mertz,

10
per questo sto ricevendo un errore - il valore jest.fn () deve essere una funzione o spia spia. Qualche idea?
Paul Fitzgerald,

3
Questo non causerà problemi se si hanno più test che usano localStorage? Non vorresti resettare le spie dopo ogni test per impedire "spillover" in altri test?
Brandon Sturgeon,

43

Attualmente (ottobre '19) localStorage non può essere deriso o spiato per scherzo come faresti di solito, e come indicato nei documenti di create-reagire-app. Ciò è dovuto alle modifiche apportate a jsdom. Puoi leggerlo nei tracker dei problemi jest e jsdom .

Per ovviare al problema, puoi invece spiare il prototipo:

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

In realtà funziona per me solo con SpyOn, non è necessario ignorare la funzione setItemjest.spyOn(window.localStorage.__proto__, 'setItem');
Yohan Dahmani

Sì, ho elencato i due come alternative, non è necessario fare entrambe le cose.
Bastian Stein,

intendevo anche senza l'override del setItem 😉
Yohan Dahmani,

Non credo di capire. Puoi chiarire per favore?
Bastian Stein,

1
Ah sì. Stavo dicendo che puoi usare la prima riga o la seconda riga. Sono alternative che fanno la stessa cosa. Qualunque sia la tua preferenza personale :) Mi dispiace per la confusione.
Bastian Stein,


13

Un'alternativa migliore che gestisce i undefinedvalori (non ha toString()) e restituisce nullse il valore non esiste. reactHo provato questo con v15 reduxeredux-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

Grazie ad Alexis Tyler per l'idea di aggiungere removeItem: developer.mozilla.org/en-US/docs/Web/API/Storage/removeItem
Dmitriy

Credere che null e undefined debbano risultare in "null" e "undefined" (stringhe letterali)
menehune23,

6

Se stai cercando un finto e non uno stub, ecco la soluzione che 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

Esporto gli elementi di archiviazione per una facile inizializzazione. IE, posso facilmente impostarlo su un oggetto

Nelle versioni più recenti di Jest + JSDom non è possibile impostare questo, ma il deposito locale è già disponibile e puoi spiarlo in questo modo:

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

5

Ho trovato questa soluzione da 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
});

Puoi inserire questo codice nei tuoi setupTest e dovrebbe funzionare bene.

L'ho provato in un progetto con typesctipt.


per me Object.defineProperty ha fatto il trucco. L'assegnazione diretta degli oggetti non ha funzionato. Grazie!
Vicens Fayos,

4

Sfortunatamente, le soluzioni che ho trovato qui non hanno funzionato per me.

Quindi stavo guardando i problemi di Jest GitHub e ho trovato questo thread

Le soluzioni più votate erano queste:

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

// or

Storage.prototype.getItem = jest.fn(() => 'bla');

I miei test non hanno windowStoragedefinito. Forse è la versione precedente di Jest che sto usando.
Antrikshy,

3

Come suggerito da @ ck4, la documentazione ha una chiara spiegazione per l'uso localStoragein jest. Tuttavia, le funzioni simulate non riuscivano a eseguire nessuno dei localStoragemetodi.

Di seguito è riportato l'esempio dettagliato del mio componente di reazione che utilizza metodi astratti per scrivere e leggere dati,

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

Errore:

TypeError: _setupLocalStorage2.default.setItem is not a function

Fix:
Aggiungere di seguito la funzione finto per scherzo (percorso: .jest/mocks/setUpStore.js)

let mockStorage = {};

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

Lo snippet è referenziato da qui


2

Ho trovato alcune altre risposte qui per risolverlo per un progetto con Typescript. Ho creato un LocalStorageMock in questo modo:

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]
    }
}

Quindi ho creato una classe LocalStorageWrapper che utilizzo per tutto l'accesso all'archiviazione locale nell'app invece di accedere direttamente alla variabile di archiviazione locale globale. Ha reso facile impostare il finto nell'involucro per i test.


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 finto e aggiungilo globalall'oggetto


2

È possibile utilizzare questo approccio per evitare beffe.

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

2

Devi deridere l'archiviazione locale con questi frammenti

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

E nella jest config:

"setupFiles":["localStorage.js"]

Sentiti libero di chiedere qualsiasi cosa .


1

La seguente soluzione è compatibile per i test con TypeScript, ESLint, TSLint e Prettier più severi { "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 per come aggiornare global.localStorage


1

Per fare lo stesso nel dattiloscritto, procedi come segue:

Imposta un file con i seguenti contenuti:

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

Quindi aggiungi la seguente riga a package.json sotto le tue configurazioni Jest

"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",

Oppure si importa questo file nel test case in cui si desidera deridere il deposito locale.


0

Questo ha funzionato per me,

delete global.localStorage;
global.localStorage = {
getItem: () => 
 }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.