Bagaimana cara saya menangani localStorage dalam tes bercanda?


144

Saya terus mendapatkan "localStorage tidak didefinisikan" dalam tes Jest yang masuk akal tapi apa pilihan saya? Memukul dinding bata.

Jawaban:


141

Solusi hebat dari @chiedo

Namun, kami menggunakan sintaks ES2015 dan saya merasa sedikit lebih bersih untuk menulisnya dengan cara ini.

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
Mungkin harus dilakukan value + ''di setter untuk menangani nilai nol dan tidak terdefinisi dengan benar
menehune23

Saya pikir lelucon terbaru hanya menggunakan itu || nullsebabnya pengujian saya gagal, karena dalam pengujian saya saya gunakan not.toBeDefined(). Solusi @Chiedo membuatnya bekerja lagi
jcubic

Saya pikir ini secara teknis rintisan :) lihat di sini untuk versi mengejek: stackoverflow.com/questions/32911630/…
TigerBear

100

Cari tahu dengan bantuan ini: https://groups.google.com/forum/#!topic/jestjs/9EPhuNWVYTg

Siapkan file dengan konten berikut:

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

Kemudian Anda menambahkan baris berikut ke package.json Anda di bawah konfigurasi Jest Anda

"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",


6
Rupanya dengan salah satu pembaruan nama parameter ini berubah dan sekarang disebut "setupTestFrameworkScriptFile"
Grzegorz Pawlik

2
"setupFiles": [...]berfungsi juga. Dengan opsi array, memungkinkan memisahkan tiruan menjadi file yang terpisah. Misalnya:"setupFiles": ["<rootDir>/__mocks__/localStorageMock.js"]
Stiggler

3
Nilai kembali getItemsedikit berbeda dengan apa yang akan dikembalikan oleh browser jika tidak ada data yang ditetapkan terhadap kunci tertentu. menelepon getItem("foo")ketika tidak diatur misalnya akan kembali nulldi browser, tetapi undefineddengan tiruan ini - ini menyebabkan salah satu pengujian saya gagal. Solusi sederhana bagi saya adalah kembali store[key] || nullke getItemfungsi
Ben Broadley

ini tidak berfungsi jika Anda melakukan sesuatu sepertilocalStorage['test'] = '123'; localStorage.getItem('test')
merampok

3
Saya mendapatkan kesalahan berikut - nilai jest.fn () harus berupa fungsi tiruan atau mata-mata. Ada ide?
Paul Fitzgerald

55

Jika menggunakan aplikasi create-react, ada solusi yang lebih sederhana dan langsung dijelaskan dalam dokumentasi .

Buat src/setupTests.jsdan letakkan ini di dalamnya:

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

Kontribusi Tom Mertz dalam komentar di bawah:

Anda kemudian dapat menguji bahwa fungsi localStorageMock Anda digunakan dengan melakukan sesuatu seperti

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

di dalam tes Anda jika Anda ingin memastikan itu dipanggil. Lihat https://facebook.github.io/jest/docs/en/mock-functions.html


Hai c4k! Bisakah Anda memberi contoh bagaimana Anda akan menggunakannya dalam tes Anda?
Dimo

Maksud kamu apa ? Anda tidak perlu menginisialisasi apa pun dalam tes Anda, itu hanya mengejek secara otomatis yang localStorageAnda gunakan dalam kode Anda. (jika Anda menggunakan create-react-appdan semua skrip otomatis yang disediakannya secara alami)
c4k

Anda kemudian dapat menguji bahwa fungsi localStorageMock Anda digunakan dengan melakukan sesuatu seperti expect(localStorage.getItem).toBeCalledWith('token')atau expect(localStorage.getItem.mock.calls.length).toBe(1)di dalam tes Anda jika Anda ingin memastikan itu dipanggil. Lihat facebook.github.io/jest/docs/en/mock-functions.html
Tom Mertz

10
untuk ini saya mendapatkan error - nilai jest.fn () harus berupa fungsi tiruan atau mata-mata. Ada ide?
Paul Fitzgerald

3
Bukankah ini akan menimbulkan masalah jika Anda memiliki beberapa tes yang digunakan localStorage? Tidakkah Anda ingin mengatur ulang mata-mata setelah setiap tes untuk mencegah "limpahan" ke dalam tes lain?
Brandon Sturgeon

43

Saat ini (Oktober '19) penyimpanan lokal tidak dapat diejek atau dimata-matai dengan bercanda seperti yang biasa Anda lakukan, dan sebagaimana diuraikan dalam dokumen app-react-app. Ini karena perubahan yang dilakukan di jsdom. Anda bisa membacanya di gurauan dan kegelisahan pelacak masalah .

Sebagai solusinya, Anda dapat memata-matai prototipe sebagai gantinya:

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

Sebenarnya ini bekerja untuk saya hanya dengan mata-mata, tidak perlu mengesampingkan fungsi setItemjest.spyOn(window.localStorage.__proto__, 'setItem');
Yohan Dahmani

Ya, saya mendaftarkan keduanya sebagai alternatif, tidak perlu melakukan keduanya.
Bastian Stein

maksud saya tanpa mengesampingkan setItem juga 😉
Yohan Dahmani

Saya rasa saya tidak mengerti. Bisakah Anda mengklarifikasi?
Bastian Stein

1
Ah iya. Saya mengatakan Anda bisa menggunakan baris pertama, atau baris kedua. Mereka adalah alternatif yang melakukan hal yang sama. Apa pun preferensi pribadi Anda :) Maaf tentang kebingungan.
Bastian Stein


13

Alternatif yang lebih baik yang menangani undefinednilai (tidak ada toString()) dan kembali nulljika nilai tidak ada. Menguji ini dengan reactv15, reduxdanredux-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

Terima kasih kepada Alexis Tyler untuk ide untuk menambahkan removeItem: developer.mozilla.org/en-US/docs/Web/API/Storage/removeItem
Dmitriy

Percaya null dan undefined perlu menghasilkan "null" dan "undefined" (string literal)
menehune23

6

Jika Anda mencari tiruan dan bukan sebuah rintisan, berikut adalah solusi yang saya gunakan:

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

Saya mengekspor item penyimpanan agar mudah diinisialisasi. Yaitu saya dapat dengan mudah mengaturnya ke objek

Di versi Jest + JSDom yang lebih baru tidak mungkin untuk mengatur ini, tetapi penyimpanan lokal sudah tersedia dan Anda dapat memata-matai itu seperti ini:

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

5

Saya menemukan solusi ini dari 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
});

Anda dapat memasukkan kode ini di setupTests Anda dan itu akan berfungsi dengan baik.

Saya mengujinya dalam proyek dengan typesctipt.


bagi saya Object.defineProperty membuat trik. Penugasan objek langsung tidak berfungsi. Terima kasih!
Vicens Fayos

4

Sayangnya, solusi yang saya temukan di sini tidak berhasil untuk saya.

Jadi saya melihat masalah Jest GitHub dan menemukan utas ini

Solusi yang paling banyak dipilih adalah:

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

// or

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

Tes saya juga tidak memiliki windowatau Storagemendefinisikan. Mungkin itu versi lama Jest yang saya gunakan.
Antrikshy

3

Sebagai @ ck4 disarankan dokumentasi memiliki penjelasan yang jelas untuk digunakan localStoragedalam bercanda. Namun fungsi tiruan gagal menjalankan salah satulocalStorage metode.

Di bawah ini adalah contoh terperinci dari komponen reaksi saya yang memanfaatkan metode abstrak untuk menulis dan membaca data,

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

Kesalahan:

TypeError: _setupLocalStorage2.default.setItem is not a function

Fix:
Tambah bawah fungsi mock untuk bercanda (path: .jest/mocks/setUpStore.js)

let mockStorage = {};

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

Cuplikan direferensikan dari sini


2

Riffed off beberapa jawaban lain di sini untuk menyelesaikannya untuk proyek dengan naskah. Saya membuat LocalStorageMock seperti ini:

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

Kemudian saya membuat kelas LocalStorageWrapper yang saya gunakan untuk semua akses ke penyimpanan lokal di aplikasi alih-alih secara langsung mengakses variabel penyimpanan lokal global. Memudahkan untuk mengatur tiruan di bungkusnya untuk tes.


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

    });
});

Buat tiruan dan tambahkan ke globalobjekt


2

Anda dapat menggunakan pendekatan ini, untuk menghindari cemoohan.

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

2

Anda perlu mengejek penyimpanan lokal dengan cuplikan ini

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

Dan dalam konfigurasi bercanda:

"setupFiles":["localStorage.js"]

Jangan ragu untuk bertanya apa pun.


1

Solusi berikut ini kompatibel untuk menguji dengan ketat naskah, ESLint, TSLint, dan lebih cantik 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 untuk cara memperbarui global.localStorage


1

Untuk melakukan hal yang sama dalam naskah, lakukan hal berikut:

Siapkan file dengan konten berikut:

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

Kemudian Anda menambahkan baris berikut ke package.json Anda di bawah konfigurasi Jest Anda

"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",

Atau Anda mengimpor file ini dalam test case di mana Anda ingin mengejek penyimpanan lokal.


0

Ini berhasil untuk saya,

delete global.localStorage;
global.localStorage = {
getItem: () => 
 }
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.