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


当前回答

至少到目前为止,localStorage可以很容易地在你的笑话测试中被监视,例如:

const spyRemoveItem = jest.spyOn(window.localStorage, 'removeItem')

就是这样。你可以像以前一样使用你的间谍。

其他回答

在这里引用了一些其他的答案来解决一个用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类,我使用它来访问应用程序中的所有本地存储,而不是直接访问全局本地存储变量。便于在包装器中为测试设置模拟。

对于Jest, 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 };
};

我的组件:

export const Component = () => {
    const foo = localStorage.getItem('foo')
    localStorage.setItem('bar', 'true')
    return <h1>{foo}</h1>
}

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

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 = "true"
    const key = "bar"
    render(<Component />);
    expect(setItemMock).toHaveBeenCalledWith(key, value);
}

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

所以我在看Jest GitHub的问题,发现了这个帖子

得到最多好评的解决方案是:

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

// or

Storage.prototype.getItem = jest.fn(() => 'bla');
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);

    });
});

创建一个模拟并将其添加到全局对象

首先,我创建了一个名为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');
    });

  });
});