Wie gehe ich bei Scherztests mit localStorage um?


144

In Jest-Tests wird immer wieder "localStorage is not defined" angezeigt. Dies ist sinnvoll, aber welche Optionen stehen mir zur Verfügung? Ziegelmauern treffen.

Antworten:


141

Tolle Lösung von @chiedo

Wir verwenden jedoch die ES2015-Syntax und ich fand es etwas sauberer, sie so zu schreiben.

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
Sollte wahrscheinlich value + ''im Setter tun , um null und undefinierte Werte korrekt zu behandeln
menehune23

Ich denke, dass der letzte Scherz nur das benutzte, || nulldeshalb schlug mein Test fehl, weil ich in meinem Test benutzte not.toBeDefined(). @ Chiedo Lösung machen es wieder funktionieren
jcubic

Ich denke, das ist technisch gesehen ein Stub :) siehe hier für die verspottete Version: stackoverflow.com/questions/32911630/…
TigerBear

100

Ich habe es mit Hilfe davon herausgefunden: https://groups.google.com/forum/#!topic/jestjs/9EPhuNWVYTg

Richten Sie eine Datei mit folgendem Inhalt ein:

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

Anschließend fügen Sie Ihrer package.json unter Ihren Jest-Konfigurationen die folgende Zeile hinzu

"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",


6
Anscheinend hat sich mit einem der Updates der Name dieses Parameters geändert und jetzt heißt er "setupTestFrameworkScriptFile"
Grzegorz Pawlik

2
"setupFiles": [...]funktioniert auch. Ermöglicht mit der Array-Option das Trennen von Mocks in separate Dateien. ZB:"setupFiles": ["<rootDir>/__mocks__/localStorageMock.js"]
Stiggler

3
Der Rückgabewert von getItemunterscheidet sich geringfügig von dem, der von einem Browser zurückgegeben wird, wenn für einen bestimmten Schlüssel keine Daten festgelegt werden. Ein Aufruf, getItem("foo")wenn er nicht festgelegt ist, wird beispielsweise nullin einem Browser zurückgegeben, aber undefineddurch dieses Modell hat dies dazu geführt, dass einer meiner Tests fehlgeschlagen ist. Eine einfache Lösung für mich war, store[key] || nullin die getItemFunktion zurückzukehren
Ben Broadley

Dies funktioniert nicht, wenn Sie so etwas tunlocalStorage['test'] = '123'; localStorage.getItem('test')
Rob

3
Ich erhalte die folgende Fehlermeldung: Der Wert von jest.fn () muss eine Scheinfunktion oder ein Spion sein. Irgendwelche Ideen?
Paul Fitzgerald

55

Bei Verwendung der Create-React-App wird in der Dokumentation eine einfachere und unkompliziertere Lösung erläutert .

Erstellen Sie dies src/setupTests.jsund fügen Sie es ein:

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

Tom Mertz Beitrag in einem Kommentar unten:

Sie können dann testen, ob die Funktionen Ihres localStorageMock verwendet werden, indem Sie Folgendes tun

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

innerhalb Ihrer Tests, wenn Sie sicherstellen wollten, dass es aufgerufen wurde. Überprüfen Sie https://facebook.github.io/jest/docs/en/mock-functions.html


Hallo c4k! Könnten Sie bitte ein Beispiel geben, wie Sie das in Ihren Tests verwenden würden?
Dimo

Was meinst du ? Sie müssen in Ihren Tests nichts initialisieren, es verspottet nur automatisch das, was localStorageSie in Ihrem Code verwenden. (Wenn Sie create-react-appalle automatischen Skripte verwenden, die es natürlich bereitstellt)
c4k

Sie können dann testen, ob die Funktionen Ihres localStorageMock verwendet werden, indem Sie so etwas wie expect(localStorage.getItem).toBeCalledWith('token')oder expect(localStorage.getItem.mock.calls.length).toBe(1)innerhalb Ihrer Tests ausführen , wenn Sie sicherstellen möchten , dass es aufgerufen wurde. Überprüfen Sie facebook.github.io/jest/docs/en/mock-functions.html
Tom Mertz

10
Dafür erhalte ich einen Fehler - der Wert von jest.fn () muss eine Scheinfunktion oder ein Spion sein. Irgendwelche Ideen?
Paul Fitzgerald

3
Wird dies nicht zu Problemen führen, wenn Sie mehrere Tests verwenden localStorage? Möchten Sie die Spione nicht nach jedem Test zurücksetzen, um ein "Überlaufen" in andere Tests zu verhindern?
Brandon Sturgeon

43

Derzeit (19. Oktober) kann localStorage nicht wie gewohnt und in den Dokumenten zum Erstellen und Reagieren von Apps verspottet oder ausspioniert werden. Dies ist auf Änderungen in jsdom zurückzuführen. Sie können darüber im Scherz und in jsdom lesen Issue Tracker .

Um dieses Problem zu umgehen, können Sie stattdessen den Prototyp ausspionieren:

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

Eigentlich funktioniert es für mich nur mit dem spyOn, keine Notwendigkeit, die setItem-Funktion zu überschreibenjest.spyOn(window.localStorage.__proto__, 'setItem');
Yohan Dahmani

Ja, ich habe die beiden als Alternativen aufgeführt, ohne dass ich beides tun muss.
Bastian Stein

Ich meinte auch ohne die Außerkraftsetzung des SetItem 😉
Yohan Dahmani

Ich glaube nicht, dass ich verstehe. Können Sie bitte klarstellen?
Bastian Stein

1
Ah ja. Ich habe gesagt, Sie können entweder die erste oder die zweite Zeile verwenden. Sie sind Alternativen, die dasselbe tun. Was auch immer Ihre persönliche Präferenz ist :) Entschuldigen Sie die Verwirrung.
Bastian Stein


13

Eine bessere Alternative, die undefinedWerte verarbeitet (nicht vorhanden toString()) und zurückgibt, nullwenn kein Wert vorhanden ist. Getestet mit reactv15 reduxundredux-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

Vielen Dank an Alexis Tyler für die Idee, Folgendes hinzuzufügen removeItem: developer.mozilla.org/en-US/docs/Web/API/Storage/removeItem
Dmitriy

Glauben Sie, dass null und undefiniert zu "null" und "undefiniert" (wörtliche Zeichenfolgen) führen müssen
menehune23

6

Wenn Sie nach einem Mock und nicht nach einem Stub suchen, ist hier die Lösung, die ich verwende:

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

Ich exportiere die Speicherelemente zur einfachen Initialisierung. IE Ich kann es leicht auf ein Objekt setzen

In den neueren Versionen von Jest + JSDom ist es nicht möglich, dies festzulegen, aber der lokale Speicher ist bereits verfügbar und Sie können ihn wie folgt ausspionieren:

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

5

Ich habe diese Lösung von Github gefunden

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

Sie können diesen Code in Ihre setupTests einfügen und er sollte einwandfrei funktionieren.

Ich habe es in einem Projekt mit Typoskript getestet.


Für mich hat Object.defineProperty den Trick gemacht. Die direkte Objektzuweisung hat nicht funktioniert. Vielen Dank!
Vicens Fayos

4

Leider haben die Lösungen, die ich hier gefunden habe, bei mir nicht funktioniert.

Also habe ich mir Jest GitHub-Probleme angesehen und diesen Thread gefunden

Die am besten bewerteten Lösungen waren diese:

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

// or

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

Meine Tests haben windowoder haben auch nicht Storagedefiniert. Vielleicht ist es die ältere Version von Jest, die ich benutze.
Antrikshy

3

Wie in @ ck4 vorgeschlagen, enthält die Dokumentation eine klare Erklärung für die Verwendung localStorageim Scherz. Die Scheinfunktionen konnten jedoch keine der folgenden Funktionen ausführenlocalStorage Methoden .

Unten ist das detaillierte Beispiel meiner Reaktionskomponente, die abstrakte Methoden zum Schreiben und Lesen von Daten verwendet.

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

Fix:
In unter Mock - Funktion für Scherz (Pfad: .jest/mocks/setUpStore.js)

let mockStorage = {};

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

Von hier aus wird auf das Snippet verwiesen


2

Hier wurden einige andere Antworten abgefragt, um sie für ein Projekt mit Typescript zu lösen. Ich habe einen LocalStorageMock wie folgt erstellt:

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

Dann habe ich eine LocalStorageWrapper-Klasse erstellt, die ich für den gesamten Zugriff auf den lokalen Speicher in der App verwende, anstatt direkt auf die globale lokale Speichervariable zuzugreifen. Es war einfach, den Mock für Tests in den Wrapper zu setzen.


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

    });
});

Erstellen Sie ein globalModell und fügen Sie es dem Objekt hinzu


2

Sie können diesen Ansatz verwenden, um Spott zu vermeiden.

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

2

Sie müssen den lokalen Speicher mit diesen Snippets verspotten

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

Und in der Scherzkonfiguration:

"setupFiles":["localStorage.js"]

Fühlen Sie sich frei, etwas zu fragen.


1

Die folgende Lösung ist kompatibel zum Testen mit strengerer Maschinenschrift, ESLint, TSLint und Prettier config: { "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 zum Aktualisieren von global.localStorage


1

Gehen Sie wie folgt vor, um dasselbe im Typoskript zu tun:

Richten Sie eine Datei mit folgendem Inhalt ein:

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

Anschließend fügen Sie Ihrer package.json unter Ihren Jest-Konfigurationen die folgende Zeile hinzu

"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",

Oder Sie importieren diese Datei in Ihren Testfall, in dem Sie den lokalen Speicher verspotten möchten.


0

Das hat bei mir funktioniert,

delete global.localStorage;
global.localStorage = {
getItem: () => 
 }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.