Как мне работать с localStorage в шутливых тестах?

148

Я продолжаю получать сообщение «localStorage is not defined» в тестах Jest, что имеет смысл, но каковы мои варианты? Удар по кирпичным стенам.

Chiedo
источник

Ответы:

146

Отличное решение от @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
103

Разобрался с помощью этого: 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",

Chiedo
источник
6
Видимо с одним из обновлений название этого параметра изменилось и теперь он называется "setupTestFrameworkScriptFile"
Гжегож Павлик
2
"setupFiles": [...]тоже работает. С опцией массива позволяет разделять макеты на отдельные файлы. Например:"setupFiles": ["<rootDir>/__mocks__/localStorageMock.js"]
Stiggler
4
Возвращаемое значение getItemнемного отличается от того, что было бы возвращено браузером, если для определенного ключа не заданы данные. вызов, getItem("foo")когда он не установлен, например, вернется nullв браузере, но из- undefinedза этого макета - это привело к сбою одного из моих тестов. Простым решением для меня было вернуться store[key] || nullв getItemфункцию
Бен Бродли
это не сработает, если вы сделаете что-то вродеlocalStorage['test'] = '123'; localStorage.getItem('test')
rob
3
Я получаю следующую ошибку - значение jest.fn () должно быть фиктивной функцией или шпионским. Любые идеи?
Пол Фицджеральд
57

При использовании создания реагирующего-приложения, есть более простое и простое решение описано в документации .

Создайте 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
источник
Привет 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
Tom Mertz
12
для этого я получаю сообщение об ошибке - значение jest.fn () должно быть фиктивной функцией или шпионским. Любые идеи?
Пол Фицджеральд
3
Не вызовет ли это проблем, если вы используете несколько тестов localStorage? Разве вы не хотели бы сбрасывать шпионов после каждого теста, чтобы предотвратить «перетекание» в другие тесты?
Brandon Sturgeon
50

В настоящее время (октябрь '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();
Бастиан Штайн
источник
На самом деле у меня это работает только со spyOn, нет необходимости переопределять функцию setItemjest.spyOn(window.localStorage.__proto__, 'setItem');
Йохан Дахмани
Да, я перечислил оба варианта в качестве альтернативы, нет необходимости использовать оба варианта.
Бастиан Штайн,
Я также имел в виду без переопределения setItem 😉
Йохан Дахмани,
1
О да. Я говорил, что вы можете использовать либо первую, либо вторую строчку. Это альтернативы, которые делают то же самое. Независимо от ваших личных предпочтений :) Извините за путаницу.
Бастиан Штайн,
1
FWIW, eslint теперь говорит, что obj .__ proto__ устарел, и лучше вместо него использовать Object.getPrototypeOf (obj). Похоже, здесь это тоже работает.
Кен Проновичи,
14

Лучшая альтернатива, которая обрабатывает 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
Дмитрий
источник
Спасибо Alexis Tyler за идею добавить removeItem: developer.mozilla.org/en-US/docs/Web/API/Storage/removeItem
Дмитрий
Believe нуля и неопределенная необходимости привести в «нулевой» и «неопределенной» (символьные строках)
menehune23
14

или вы просто возьмете такой макет пакета:

https://www.npmjs.com/package/jest-localstorage-mock

он обрабатывает не только функции хранилища, но также позволяет проверить, действительно ли было вызвано хранилище.

Алигертор
источник
8

Если вы ищете макет, а не заглушку, вот решение, которое я использую:

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');
ТигрМедведь
источник
8

К сожалению, решения, которые я здесь нашел, у меня не сработали.

Итак, я смотрел на проблемы Jest GitHub и нашел эту ветку

Наибольшее количество голосов получили следующие решения:

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

// or

Storage.prototype.getItem = jest.fn(() => 'bla');
Кристиан Сайки
источник
Мои тесты не имеют windowили Storageлюбой. Возможно, я использую старую версию Jest.
Антрикшы
6

Я нашел это решение с 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 сделал трюк. Прямое назначение объекта не сработало. Благодарность!
Vicens Fayos
как разобраться с сервисом, который получает данные из localStorage?
Дармаван Зулкифли
вы должны использовать фиктивные данные вместо сервисов для тестирования. В модульных тестах вы должны тестировать одну функциональность.
Карлос Уамани,
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 = {}
};

Ссылка на фрагмент отсюда

Mad-D
источник
3

Вы можете использовать этот подход, чтобы избежать насмешек.

Storage.prototype.getItem = jest.fn(() => expectedPayload);
Санат
источник
как настроить какую-то службу для загрузки данных из localStorage? Вот мой вопрос stackoverflow.com/questions/63716411/…
Дармаван Зулкифли
3

Вам нужно имитировать локальное хранилище с помощью этих фрагментов

// 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"]

Не стесняйтесь спрашивать о чем угодно.

Тонкий кодер
источник
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, который использую для всего доступа к локальному хранилищу в приложении вместо прямого доступа к глобальной локальной переменной хранилища. Упростили установку макета в оболочку для тестов.

CorayThan
источник
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);

    });
});

Создайте макет и добавьте его к globalобъекту

Тревор Джозеф
источник
2

Чтобы сделать то же самое в машинописном тексте, сделайте следующее:

Установите файл со следующим содержимым:

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",

Или вы импортируете этот файл в свой тестовый пример, где хотите имитировать локальное хранилище.

vs_lala
источник
1

Следующее решение совместимо для тестирования с более строгими конфигурациями 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

Бо Смит
источник
-1

Это сработало для меня,

delete global.localStorage;
global.localStorage = {
getItem: () => 
 }
пратичный
источник