Я продолжаю получать сообщение «localStorage is not defined» в тестах Jest, что имеет смысл, но каковы мои варианты? Удар по кирпичным стенам.
Ответы:
Отличное решение от @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;
|| nullименно поэтому мой тест провалился, потому что в моем тесте я использовал not.toBeDefined(). Решение @Chiedo заставит его снова работать
Разобрался с помощью этого: 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 });
Затем вы добавляете следующую строку в свой package.json в конфигурациях Jest
"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",
"setupFiles": [...]тоже работает. С опцией массива позволяет разделять макеты на отдельные файлы. Например:"setupFiles": ["<rootDir>/__mocks__/localStorageMock.js"]
getItemнемного отличается от того, что было бы возвращено браузером, если для определенного ключа не заданы данные. вызов, getItem("foo")когда он не установлен, например, вернется nullв браузере, но из- undefinedза этого макета - это привело к сбою одного из моих тестов. Простым решением для меня было вернуться store[key] || nullв getItemфункцию
localStorage['test'] = '123'; localStorage.getItem('test')
При использовании создания реагирующего-приложения, есть более простое и простое решение описано в документации .
Создайте 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
localStorageвы используете в своем коде. (если вы используете create-react-appвсе автоматические скрипты, которые он предоставляет, естественно)
expect(localStorage.getItem).toBeCalledWith('token')или expect(localStorage.getItem.mock.calls.length).toBe(1)внутри ваших тестов, если вы хотите убедиться, что он был вызван. Проверьте facebook.github.io/jest/docs/en/mock-functions.html
localStorage? Разве вы не хотели бы сбрасывать шпионов после каждого теста, чтобы предотвратить «перетекание» в другие тесты?
В настоящее время (октябрь '19) localStorage нельзя высмеивать или шпионить с помощью шуток, как это обычно делается и как указано в документации create-response-app. Это связано с изменениями, внесенными в jsdom. Вы можете прочитать об этом в трекерах проблем jest и 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();
jest.spyOn(window.localStorage.__proto__, 'setItem');
Лучшая альтернатива, которая обрабатывает 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
или вы просто возьмете такой макет пакета:
https://www.npmjs.com/package/jest-localstorage-mock
он обрабатывает не только функции хранилища, но также позволяет проверить, действительно ли было вызвано хранилище.
Если вы ищете макет, а не заглушку, вот решение, которое я использую:
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');
К сожалению, решения, которые я здесь нашел, у меня не сработали.
Итак, я смотрел на проблемы Jest GitHub и нашел эту ветку
Наибольшее количество голосов получили следующие решения:
const spy = jest.spyOn(Storage.prototype, 'setItem');
// or
Storage.prototype.getItem = jest.fn(() => 'bla');
windowили Storageлюбой. Возможно, я использую старую версию Jest.
Я нашел это решение с 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.
Как @ 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 = {}
};
Ссылка на фрагмент отсюда
Вы можете использовать этот подход, чтобы избежать насмешек.
Storage.prototype.getItem = jest.fn(() => expectedPayload);
Вам нужно имитировать локальное хранилище с помощью этих фрагментов
// 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
});
И в конфиге шутки:
"setupFiles":["localStorage.js"]
Не стесняйтесь спрашивать о чем угодно.
Отказался от некоторых других ответов здесь, чтобы решить эту проблему для проекта с 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, который использую для всего доступа к локальному хранилищу в приложении вместо прямого доступа к глобальной локальной переменной хранилища. Упростили установку макета в оболочку для тестов.
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);
});
});
Создайте макет и добавьте его к globalобъекту
Чтобы сделать то же самое в машинописном тексте, сделайте следующее:
Установите файл со следующим содержимым:
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 });
Затем вы добавляете следующую строку в свой package.json в конфигурациях Jest
"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",
Или вы импортируете этот файл в свой тестовый пример, где хотите имитировать локальное хранилище.
Следующее решение совместимо для тестирования с более строгими конфигурациями TypeScript, ESLint, TSLint и Prettier { "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
value + ''в инкубаторе с ручкой нуль и неопределенными значениями правильно