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


148

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

Ответы:


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


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! Не могли бы вы привести пример, как вы бы использовали это в своих тестах?
Димо

Что вы имеете в виду ? Вам не нужно ничего инициализировать в ваших тестах, он просто автоматически имитирует то, что 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


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 = {}
};

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



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


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

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


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


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.