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.

Chiedo
sumber

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

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

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

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

Gila-D
sumber
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.

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

Trevor Joseph
sumber
2

Anda dapat menggunakan pendekatan ini, untuk menghindari cemoohan.

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

Slim Coder
sumber
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

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

vs_lala
sumber
0

Ini berhasil untuk saya,

delete global.localStorage;
global.localStorage = {
getItem: () => 
 }
prati
sumber