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

时间:2015-10-02 16:25:09

标签: jestjs

我一直在Jest测试中得到“localStorage未定义”这有意义,但我的选择是什么?撞砖墙。

19 个答案:

答案 0 :(得分:115)

来自@chiedo

的出色解决方案

但是,我们使用ES2015语法,我觉得以这种方式编写它会更清晰。

{{1}}

答案 1 :(得分:76)

在此帮助下计算出来: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 });

然后在Jest configs下将以下行添加到package.json

"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",

答案 2 :(得分:44)

如果使用create-react-app,documentation中会解释一个更简单直接的解决方案。

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

const localStorageMock = {
  getItem: jest.fn(),
  setItem: jest.fn(),
  clear: jest.fn()
};
global.localStorage = localStorageMock;

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

然后您可以通过执行类似

的操作来测试您的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

答案 3 :(得分:13)

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

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

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

答案 4 :(得分:12)

当前(19年1月)不能像平常一样由玩笑来嘲笑或监视localStorage,正如create-react-app文档中所述。这是由于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();

答案 5 :(得分:11)

处理undefined值的更好的替代方案(它没有toString())并且如果值不存在则返回null。使用react v15,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

答案 6 :(得分:9)

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

所以我正在研究Jest GitHub问题,并发现了这个thread

最受好评的解决方案是:

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

// or

Storage.prototype.getItem = jest.fn(() => 'bla');

答案 7 :(得分:3)

我想我会在带有 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')
    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 = "value"
    const key = "key"
    render(<Component />);
    expect(setItemMock).toHaveBeenCalledWith(key, value);
}

答案 8 :(得分: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"]

随便问什么。

答案 9 :(得分:2)

正如@ ck4建议documentation明确解释在jest中使用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添加以下模拟函数(路径:.jest/mocks/setUpStore.js

let mockStorage = {};

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

代码段引自here

答案 10 :(得分:2)

我从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中,它应该可以正常工作。

我在一个类型为ctipt的项目中对其进行了测试。

答案 11 :(得分:1)

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

interface Spies {
  [key: string]: jest.SpyInstance
}

describe('→ Local storage', () => {

  const spies: Spies = {}

  beforeEach(() => {
    ['setItem', 'getItem', 'clear'].forEach((fn: string) => {
      const mock = jest.fn(localStorage[fn])
      spies[fn] = jest.spyOn(Storage.prototype, fn).mockImplementation(mock)
    })
  })

  afterEach(() => {
    Object.keys(spies).forEach((key: string) => spies[key].mockRestore())
  })

  test('→ setItem ...', async () => {
      localStorage.setItem( 'foo', 'bar' )
      expect(localStorage.getItem('foo')).toEqual('bar')
      expect(spies.setItem).toHaveBeenCalledTimes(1)
  })
})

答案 12 :(得分:1)

要在Typescript中执行相同操作,请执行以下操作:

设置具有以下内容的文件:

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 });

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

"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",

或者您可以在要模拟本地存储的测试用例中导入此文件。

答案 13 :(得分:1)

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

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 + JSm的较新版本中,无法进行设置,但是localstorage已经可用,您可以像这样监视它:

const setItemSpy = jest.spyOn(Object.getPrototypeOf(window.localStorage), 'setItem');

答案 14 :(得分:1)

在这里摒弃其他一些答案来解决一个带有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类,用于对应用程序中的本地存储进行所有访问,而不是直接访问全局本地存储变量。可以很容易地在包装器中设置模拟以进行测试。

答案 15 :(得分:0)

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

Storage.prototype.getItem = jest.fn(() => expectedPayload);

答案 16 :(得分:0)

    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对象中

答案 17 :(得分:0)

以下解决方案与更严格的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

答案 18 :(得分:-1)

这对我有用,

delete global.localStorage;
global.localStorage = {
getItem: () => 
 }