如何在开玩笑测试中处理localStorage?

Chi*_*edo 125 jestjs

我一直在Jest测试中得到"localStorage is not defined",这是有道理的,但我的选择是什么?打砖墙.

小智 130

@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;
Run Code Online (Sandbox Code Playgroud)

  • 应该在setter中执行`value +''`来正确处理null和undefined值 (7认同)
  • **2021 年更新**:对于使用 Jest@24 及更高版本的用户,localStorage 会自动被模拟。 (6认同)
  • 嗨@NiketPathak,你有这个来源吗?我在版本 24 的发行说明或文档中找不到任何有关它的信息。 (5认同)
  • **更新2022**:实际上,我认为@NiketPathak意味着jest@22及以上,因为[该版本使用jsdom@11](https://github.com/facebook/jest/blob/v22.0.0/packages/ jest-environment-jsdom/package.json#L13) 和 [在 jsdom 11.12.0 上添加了对 localStorage 和 sessionStorage 的支持](https://github.com/jsdom/jsdom/blob/master/Changelog.md#11120) (5认同)
  • 我正在使用 Jest@24,但它不可用。我必须根据解决方案进行模拟。@NiketPathak (4认同)
  • 这不是模仿,这是假的。 (2认同)

Chi*_*edo 85

在此帮助下计算出来: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 });
Run Code Online (Sandbox Code Playgroud)

然后在Jest配置下将以下行添加到package.json中

"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",

  • 显然,在其中一个更新中,此参数的名称已更改,现在称为"setupTestFrameworkScriptFile" (6认同)
  • 如果针对特定键设置了无数据,则`getItem`的返回值与浏览器返回的值略有不同.调用`getItem("foo")`当它没有设置时,例如在浏览器中返回`null`,但是这个模拟的'undefined` - 这导致我的一个测试失败.对我来说简单的解决方案是返回`store [key] || `getItem`函数中的null` (3认同)
  • `"setupFiles":[...]`也可以.使用数组选项,允许将模拟分离为单独的文件.例如:`"setupFiles":["<rootDir>/__ mocks __/localStorageMock.js"]` (2认同)
  • 如果你执行类似 `localStorage['test'] = '123'; 的操作,这将不起作用。localStorage.getItem('测试')` (2认同)
  • 我收到以下错误-jest.fn()值必须是模拟函数或间谍。有任何想法吗? (2认同)

c4k*_*c4k 51

如果使用create-react-app,则可以在文档中解释更简单直接的解决方案.

创建src/setupTests.js并将其放入其中:

const localStorageMock = {
  getItem: jest.fn(),
  setItem: jest.fn(),
  clear: jest.fn()
};
global.localStorage = localStorageMock;
Run Code Online (Sandbox Code Playgroud)

Tom Mertz在以下评论中的贡献:

然后,您可以通过执行类似操作来测试您的localStorageMock的函数

expect(localStorage.getItem).toBeCalledWith('token')
// or
expect(localStorage.getItem.mock.calls.length).toBe(1)
Run Code Online (Sandbox Code Playgroud)

如果你想确保它被调用,你的测试内部.查看https://facebook.github.io/jest/docs/en/mock-functions.html

  • 为此我得到一个错误 - jest.fn()值必须是模拟函数或间谍.有任何想法吗? (7认同)
  • 如果您有多个使用`localStorage`的测试,这不会引起问题吗?您是否不想在每次测试后重置间谍以防止“溢出”到其他测试中? (3认同)

Bas*_*ein 23

目前(Jan '19)localStorage不能像往常那样通过开玩笑来嘲笑或监视,并且如create-react-app docs中所述.这是由于在jsdom中所做的更改.你可以在这里阅读https://github.com/facebook/jest/issues/6798https://github.com/jsdom/jsdom/issues/2318.

作为一种解决方法,您可以窥探原型:

// 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();
Run Code Online (Sandbox Code Playgroud)

  • 实际上它只适用于我的间谍,不需要覆盖 setItem 函数`jest.spyOn(window.localStorage.__proto__, 'setItem');` (3认同)
  • FWIW,eslint 现在表示 obj.__proto__ 已弃用,最好使用 Object.getPrototypeOf(obj) 代替。这似乎也适用于此。 (3认同)
  • 是啊。我是说你可以使用第一行,也可以使用第二行。它们是做同样事情的替代品。无论您个人偏好如何:) 很抱歉造成混乱。 (2认同)
  • 此策略不允许您区分 localStorage 和 sessionStorage,因为两者都是 Storage 的实例并继承自 Storage 原型。 (2认同)

Chr*_*iki 17

不幸的是,我在这里找到的解决方案对我不起作用。

所以我在看 Jest GitHub 问题并找到了这个线程

最受好评的解决方案是这些:

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

// or

Storage.prototype.getItem = jest.fn(() => 'bla');
Run Code Online (Sandbox Code Playgroud)


Ali*_*tor 13

或者你只是采取这样的模拟包:

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

它不仅可以处理存储功能,还可以测试是否实际调用了存储.


Dmi*_*riy 12

处理undefined值(它没有toString())的更好的替代方法,null如果值不存在则返回.用reactv15 测试了这个,reduxredux-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
Run Code Online (Sandbox Code Playgroud)


Tig*_*ear 11

如果您正在寻找模拟而不是存根,这是我使用的解决方案:

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
Run Code Online (Sandbox Code Playgroud)

我导出存储项以便于初始化。IE 我可以很容易地将它设置为一个对象

在较新版本的 Jest + JSDom 中,无法设置此项,但是 localstorage 已经可用,您可以像这样监视它:

const setItemSpy = jest.spyOn(Object.getPrototypeOf(window.localStorage), 'setItem');
Run Code Online (Sandbox Code Playgroud)


Car*_*ani 7

我从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
});
Run Code Online (Sandbox Code Playgroud)

您可以在 setupTests 中插入此代码,它应该可以正常工作。

我在一个带有 typectipt 的项目中对其进行了测试。


Gre*_*Woz 7

使用 TypeScript 和 Jest 的更优雅的解决方案。

\n
    interface Spies {\n      [key: string]: jest.SpyInstance\n    }\n    \n    describe(\'\xe2\x86\x92 Local storage\', () => {\n    \n      const spies: Spies = {}\n    \n      beforeEach(() => {\n        [\'setItem\', \'getItem\', \'clear\'].forEach((fn: string) => {\n          const mock = jest.fn(localStorage[fn])\n          spies[fn] = jest.spyOn(Storage.prototype, fn).mockImplementation(mock)\n        })\n      })\n    \n      afterEach(() => {\n        Object.keys(spies).forEach((key: string) => spies[key].mockRestore())\n      })\n    \n      test(\'\xe2\x86\x92 setItem ...\', async () => {\n          localStorage.setItem( \'foo\', \'bar\' )\n          expect(localStorage.getItem(\'foo\')).toEqual(\'bar\')\n          expect(spies.setItem).toHaveBeenCalledTimes(1)\n      })\n    })\n
Run Code Online (Sandbox Code Playgroud)\n


Vla*_*suc 6

Object.defineProperty(window, "localStorage", {
  value: {
    getItem: jest.fn(),
    setItem: jest.fn(),
    removeItem: jest.fn(),
  },
});
Run Code Online (Sandbox Code Playgroud)

或者

jest.spyOn(Object.getPrototypeOf(localStorage), "getItem");
jest.spyOn(Object.getPrototypeOf(localStorage), "setItem");
Run Code Online (Sandbox Code Playgroud)


Ste*_*ies 6

2022 年更新。

Jest@24+ 能够自动模拟本地存储。但是,默认情况下不再附带所需的依赖项。

npm i -D jest-environment-jsdom

您还需要更改 Jest 测试模式:

// jest.config.cjs
module.exports = {
  ...
  testEnvironment: "jsdom",
  ...
};
Run Code Online (Sandbox Code Playgroud)

现在 localStorage 已经被你嘲笑了。

例子:

// myStore.js
const saveLocally = (key, value) => {
  localStorage.setItem(key, value)
};
Run Code Online (Sandbox Code Playgroud)

测试:

// myStore.spec.ts
import { saveLocally } from "./myStore.js"

it("saves key-value pair", () => {
  let key = "myKey";
  let value = "myValue";
  expect(localStorage.getItem(key)).toBe(null);
  saveLocally(key, value);
  expect(localStorage.getItem(key)).toBe(value);
};
Run Code Online (Sandbox Code Playgroud)


San*_*ath 5

您可以使用这种方法来避免嘲笑。

Storage.prototype.getItem = jest.fn(() => expectedPayload);
Run Code Online (Sandbox Code Playgroud)


小智 5

您需要使用此代码段模拟本地存储

// 本地存储.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
});
Run Code Online (Sandbox Code Playgroud)

在开玩笑的配置中:

"setupFiles":["localStorage.js"]
Run Code Online (Sandbox Code Playgroud)

随意问任何事情。


Jam*_*mie 5

我想我会在带有 React 的 TypeScript 中添加另一个对我来说非常有效的解决方案:

我创建了一个 mockLocalStorage.ts

export const mockLocalStorage = () => {
  const setItemMock = jest.fn();
  const getItemMock = jest.fn();

  beforeEach(() => {
    Storage.prototype.setItem = setItemMock;
    Storage.prototype.getItem = getItemMock;
  });

  afterEach(() => {
    setItemMock.mockRestore();
    getItemMock.mockRestore();
  });

  return { setItemMock, getItemMock };
};
Run Code Online (Sandbox Code Playgroud)

我的组件:

export const Component = () => {
    const foo = localStorage.getItem('foo')
    return <h1>{foo}</h1>
}
Run Code Online (Sandbox Code Playgroud)

然后在我的测试中我像这样使用它:

import React from 'react';

import { mockLocalStorage } from '../../test-utils';
import { Component } from './Component';

const { getItemMock, setItemMock } = mockLocalStorage();

it('fetches something from localStorage', () => {
    getItemMock.mockReturnValue('bar');
    render(<Component />);
    expect(getItemMock).toHaveBeenCalled();
    expect(getByText(/bar/i)).toBeInTheDocument()
});

it('expects something to be set in localStorage' () => {
    const value = "value"
    const key = "key"
    render(<Component />);
    expect(setItemMock).toHaveBeenCalledWith(key, value);
}
Run Code Online (Sandbox Code Playgroud)