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.

Chiedo
quelle

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;
Nickcan
quelle
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",

Chiedo
quelle
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

c4k
quelle
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();
Bastian Stein
quelle
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
Dmitriy
quelle
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');
TigerBear
quelle
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.

Carlos Huamani
quelle
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');
Christian Saiki
quelle
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

Mad-D
quelle
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.

CorayThan
quelle
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

Trevor Joseph
quelle
2

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

Storage.prototype.getItem = jest.fn(() => expectedPayload);
Sanath
quelle
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.

Schlanker Codierer
quelle
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

Beau Smith
quelle
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.

vs_lala
quelle
0

Das hat bei mir funktioniert,

delete global.localStorage;
global.localStorage = {
getItem: () => 
 }
prathisch
quelle