Як я маю справу з локальним сховищем у тестах жарту?


144

Я продовжую отримувати "localStorage не визначено" в тестах Jest, що має сенс, але які мої варіанти? Попадання цегляних стін.

Відповіді:


142

Чудове рішення від @chiedo

Однак ми використовуємо синтаксис ES2015, і я вважав, що це трохи чистіше написати це таким чином.

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
Напевно, слід робити value + ''в сетері, щоб правильно обробити нульові та невизначені значення
menehune23

Я думаю, що останній прикол просто використовував це || nullте, що тому мій тест виявився невдалим, тому що в моєму тесті я використовував not.toBeDefined(). Рішення @Chiedo змусить його працювати знову
jcubic

Я думаю , що це технічно заглушка :) подивитися тут знущалися версія: stackoverflow.com/questions/32911630 / ...
TigerBear

100

З'ясував це за допомогою цього: https://groups.google.com/forum/#!topic/jestjs/9EPhuNWVYTg

Налаштуйте файл із таким вмістом:

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

Потім ви додасте наступний рядок до свого пакета.json під вашими конфігураціями Jest

"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",


6
Мабуть, за допомогою одного з оновлень назву цього параметра змінили, і тепер він називається "setupTestFrameworkScriptFile"
Grzegorz Pawlik

2
"setupFiles": [...]працює також. За допомогою параметра масиву дозволяє розділяти макети на окремі файли. Напр .:"setupFiles": ["<rootDir>/__mocks__/localStorageMock.js"]
Стиглер

3
Значення повернення getItemдещо відрізняється від того, що було б повернуто браузером, якщо дані не встановлені проти конкретного ключа. виклик, getItem("foo")коли його не встановлено, наприклад, повернеться nullу браузері, але undefinedцим макетом - це спричинило збій одного з моїх тестів. Просте рішення для мене було повернутися store[key] || nullв getItemфункції
Бен Broadley

це не спрацює, якщо ви робите щось на кшталтlocalStorage['test'] = '123'; localStorage.getItem('test')
пограбуйте

3
Я отримую таку помилку - значення jest.fn () має бути функцією макету чи шпигуном. Будь-які ідеї?
Пол Фіцджеральд

55

Якщо ви використовуєте create-react-app, в документації пояснюється більш просте і зрозуміле рішення .

Створіть src/setupTests.jsі вкладіть це:

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

Вклад Тома Мерца в коментарі нижче:

Потім ви можете перевірити, чи використовуються функції вашого localStorageMock, роблячи щось подібне

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

всередині ваших тестів, якщо ви хочете переконатися, що його називають. Перевірте https://facebook.github.io/jest/docs/en/mock-functions.html


Привіт c4k! Скажіть, будь ласка, приклад того, як ви використовуєте це у своїх тестах?
Дімо

Що ви маєте на увазі ? Вам не потрібно нічого ініціалізувати у своїх тестах, він просто автоматично знущається над тим, який localStorageви використовуєте у своєму коді. (якщо ви використовуєте create-react-appі всі автоматичні сценарії, які він надає природно)
c4k

Потім ви можете перевірити, чи використовуються функції вашого localStorageMock, роблячи щось на кшталт expect(localStorage.getItem).toBeCalledWith('token')або expect(localStorage.getItem.mock.calls.length).toBe(1)всередині тестів, якщо ви хочете переконатися, що воно викликано. Перевірте facebook.github.io/jest/docs/en/mock-functions.html
Том Мерц

10
для цього я отримую помилку - jest.fn () значення повинно бути функцією макету або шпигуном. Будь-які ідеї?
Пол Фіцджеральд

3
Чи це не спричинить проблеми, якщо ви використовуєте кілька тестів localStorage? Чи не хочете ви скидати шпигунів після кожного тесту, щоб запобігти «переливанню» на інші тести?
Брендон

43

В даний час (жовтень 1919 р.) Місцевим сховищем не можна глузувати або шпигувати на жарт, як зазвичай, як це було зазначено в документах "create-react-app". Це пов’язано зі змінами, внесеними в jsdom. Ви можете прочитати про це в жарт і jsdom трекерів випуску.

Для вирішення цього питання ви можете шпигувати за прототипом:

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

Насправді це працює для мене просто зі шпигуном, не потрібно перекривати функцію setItemjest.spyOn(window.localStorage.__proto__, 'setItem');
Йохан Дахмані

Так, я вказав обох як альтернативу, не потрібно робити обох.
Бастіан Штейн

я мав на увазі і без перебору серіалу. 😉
Йохан Дахмані

Я не думаю, що я розумію. Ви можете уточнити, будь ласка?
Бастіан Штейн

1
Ага так. Я казав, що ви можете використовувати або перший, або другий. Вони є альтернативами, які роблять те саме. Незалежно від ваших особистих переваг :) Вибачте за плутанину.
Бастіан Штейн


13

Краща альтернатива, яка обробляє undefinedзначення (у них немає toString()) і повертає, nullякщо значення не існує. Випробували це за допомогою reactv15 reduxтаredux-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

Дякуємо Алексіс Тайлер за ідею додати removeItem: developer.mozilla.org/en-US/docs/Web/API/Storage/removeItem
Дмитро

Вважайте нульовою та невизначеною потребою результат "null" та "undefined" (буквальні рядки)
menehune23

6

Якщо ви шукаєте макет, а не заглушку, ось таке рішення я використовую:

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

Я експортую елементи зберігання для легкої ініціалізації. IE Я можу легко встановити його на об'єкт

У нових версіях Jest + JSDom це неможливо встановити, але локальне зберігання вже доступне, і ви можете шпигувати за ним так:

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

5

Я знайшов це рішення у 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
});

Ви можете вставити цей код у свою програму setupTests, і він повинен справно працювати.

Я перевірив це в проекті з typectipt.


для мене Object.defineProperty зробив трюк. Присвоєння прямого об'єкта не спрацювало. Дякую!
Вікенс Файос

4

На жаль, рішення, які я знайшов тут, не працювали для мене.

Тому я переглядав проблеми Jest GitHub і знайшов цю тему

Найбільш актуальні рішення були такі:

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

// or

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

Мої тести також не мають windowабо не Storageвизначаються. Можливо, це стара версія Jest, яку я використовую.
Антрікші

3

Як @ ck4 запропонована документація має чітке пояснення для використання localStorageв жарті. Однак функції макету не змогли виконати жоден із localStorageметодів.

Нижче наведено докладний приклад мого компонента реакції, який використовує абстрактні методи для запису та читання даних,

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

Помилка:

TypeError: _setupLocalStorage2.default.setItem is not a function

Виправлення:
Додайте нижче макет функції для жарту (шляху: .jest/mocks/setUpStore.js)

let mockStorage = {};

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

Звідси посилається сюди


3

Ви можете використовувати такий підхід, щоб уникнути глузування.

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

2

Тут викладені деякі інші відповіді, щоб вирішити це для проекту з Typescript. Я створив LocalStorageMock так:

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

Потім я створив клас LocalStorageWrapper, який використовую для доступу до локальної пам’яті в додатку, а не для прямого доступу до глобальної змінної локального зберігання. Це дозволило легко встановити макет в обгортці для тестів.


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

    });
});

Створіть макет і додайте його до globalobjectt


2

Потрібно знущатися над місцевим сховищем за допомогою цих фрагментів

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

І в конфігурації vic:

"setupFiles":["localStorage.js"]

Сміливо запитайте що-небудь.


1

Наступне рішення сумісне для тестування з більш жорсткими TypeScript, ESLint, TSLint та 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 про те, як оновити global.localStorage


1

Щоб зробити те ж саме в Typescript, виконайте наступне:

Налаштуйте файл із таким вмістом:

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

Потім ви додасте наступний рядок до свого пакета.json під вашими конфігураціями Jest

"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",

Або ви імпортуєте цей файл у свій тестовий випадок, коли ви хочете знущатися над локальним сховищем.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.