在Jest测试中,我一直得到“localStorage is not defined”,这是有意义的,但我的选项是什么?碰壁。


当前回答

正如@ck4所建议的那样,文档已经清楚地解释了如何使用localStorage。然而,模拟函数无法执行任何localStorage方法。

下面是我的react组件的详细示例,它使用抽象方法来写入和读取数据,

//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添加以下mock函数(路径:.jest/mocks/setUpStore.js)

let mockStorage = {};

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

Snippet从这里引用

其他回答

答:

目前(7月22日)localStorage不能像你通常会被嘲笑或监视,并在创建-反应-应用程序文档中概述。这是由于jsdom中所做的更改。你可以在jest和jsdom问题跟踪器中读到它。

作为一种变通方法,你可以监视原型:

// does not work:
jest.spyOn(localStorage, "setItem");
localStorage.setItem = jest.fn();

// either of these lines will work, different syntax that does the same thing:
jest.spyOn(Storage.prototype, 'setItem');
Storage.prototype.setItem = jest.fn();

// assertions as usual:
expect(localStorage.setItem).toHaveBeenCalled();

关于监视原型机的注意事项:

监视实例使您能够观察和模拟特定对象的行为。

另一方面,监视原型,将同时观察/操作该类的每个实例。除非您有一个特殊的用例,否则这可能不是您想要的。

但是,在本例中没有区别,因为localStorage只存在一个实例。

首先,我创建了一个名为localStorage.ts(localStorage.js)的文件

class LocalStorageMock {
  store: Store;
  length: number;

  constructor() {
    this.store = {};
    this.length = 0;
  }

  key(n: number): any {
    if (typeof n === 'undefined') {
      throw new Error(
        "Uncaught TypeError: Failed to execute 'key' on 'Storage': 1 argument required, but only 0 present."
      );
    }

    if (n >= Object.keys(this.store).length) {
      return null;
    }

    return Object.keys(this.store)[n];
  }

  getItem(key: string): Store | null {
    if (!Object.keys(this.store).includes(key)) {
      return null;
    }

    return this.store[key];
  }

  setItem(key: string, value: any): undefined {
    if (typeof key === 'undefined' && typeof value === 'undefined') {
      throw new Error(
        "Uncaught TypeError: Failed to execute 'setItem' on 'Storage': 2 arguments required, but only 0 present."
      );
    }

    if (typeof value === 'undefined') {
      throw new Error(
        "Uncaught TypeError: Failed to execute 'setItem' on 'Storage': 2 arguments required, but only 1 present."
      );
    }

    if (!key) return undefined;

    this.store[key] = value.toString() || '';
    this.length = Object.keys(this.store).length;

    return undefined;
  }

  removeItem(key: string): undefined {
    if (typeof key === 'undefined') {
      throw new Error(
        "Uncaught TypeError: Failed to execute 'removeItem' on 'Storage': 1 argument required, but only 0 present."
      );
    }

    delete this.store[key];
    this.length = Object.keys(this.store).length;
    return undefined;
  }

  clear(): undefined {
    this.store = {};
    this.length = 0;

    return undefined;
  }
}

export const getLocalStorageMock = (): any => {
  return new LocalStorageMock();
};

global.localStorage = new LocalStorageMock();

然后创建名为session.test.ts的测试文件(session.test.js)

import { getLocalStorageMock } from '../localstorage';
describe('session storage', () => {
  let localStorage;
  beforeEach(() => {
    localStorage = getLocalStorageMock();
  });

  describe('getItem', () => {
    it('should return null if the item is undefined', () => {
      expect(localStorage.getItem('item')).toBeNull();
    });
    it("should return '' instead of null", () => {
      localStorage.setItem('item', '');
      expect(localStorage.getItem('item')).toBe('');
    });
    it('should return navid', () => {
      localStorage.setItem('item', 'navid');
      expect(localStorage.getItem('item')).toBe('navid');
    });

  });
});

@chiedo的好解决方案

然而,我们使用ES2015语法,我觉得这样写会更简洁一些。

class LocalStorageMock {
  constructor() {
    this.store = {};
  }

  clear() {
    this.store = {};
  }

  getItem(key) {
    return this.store[key] || null;
  }

  setItem(key, value) {
    this.store[key] = String(value);
  }

  removeItem(key) {
    delete this.store[key];
  }
}

global.localStorage = new LocalStorageMock;

如果你正在寻找一个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

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

在Jest + JSDom的新版本中,不可能设置这个,但是本地存储已经可用,你可以像这样监视它:

const setItemSpy = jest.spyOn(Object.getPrototypeOf(window.localStorage), 'setItem');
Object.defineProperty(window, "localStorage", {
  value: {
    getItem: jest.fn(),
    setItem: jest.fn(),
    removeItem: jest.fn(),
  },
});

or

jest.spyOn(Object.getPrototypeOf(localStorage), "getItem");
jest.spyOn(Object.getPrototypeOf(localStorage), "setItem");