【问题标题】:How do I deal with localStorage in jest tests?如何在玩笑测试中处理 localStorage?
【发布时间】:2015-12-30 22:15:21
【问题描述】:

我在 Jest 测试中不断收到“localStorage 未定义”,这是有道理的,但我的选择是什么?撞砖墙。

【问题讨论】:

    标签: jestjs


    【解决方案1】:

    在此帮助下解决了这个问题: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 配置下的 package.json 中添加以下行

    "setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",

    【讨论】:

    • 显然随着其中一个更新,此参数的名称发生了变化,现在它被称为“setupTestFrameworkScriptFile”
    • "setupFiles": [...] 也可以。使用数组选项,允许将模拟分成单独的文件。例如:"setupFiles": ["<rootDir>/__mocks__/localStorageMock.js"]
    • getItem 的返回值与未针对特定键设置数据时浏览器返回的值略有不同。例如,在未设置时调用 getItem("foo") 将在浏览器中返回 null,但通过此模拟调用 undefined - 这导致我的一个测试失败。对我来说简单的解决方案是在 getItem 函数中返回 store[key] || null
    • 如果您执行localStorage['test'] = '123'; localStorage.getItem('test')之类的操作,这将不起作用
    • 我收到以下错误 - jest.fn() 值必须是模拟函数或间谍。有什么想法吗?
    【解决方案2】:

    @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;
    

    【讨论】:

    • 应该在setter中做value + ''来正确处理空值和未定义值
    • 我认为最新的玩笑只是使用 || null,这就是我的测试失败的原因,因为在我的测试中我使用的是 not.toBeDefined()。 @Chiedo 解决方案让它再次工作
    • 2021 年更新:对于使用 Jest@24 及更高版本的人,localStorage 会自动模拟。
    • 嗨@NiketPathak,你有这个来源吗?我在版本 24 的发行说明或文档中找不到任何相关内容。
    • 我正在使用 Jest@24,但它不可用。我不得不根据解决方案进行模拟。 @NiketPathak
    【解决方案3】:

    处理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
    

    【讨论】:

    【解决方案4】:

    或者你只需​​要一个这样的模拟包:

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

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

    【讨论】:

      【解决方案5】:

      在这里为使用 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 类,用于对应用程序中的本地存储进行所有访问,而不是直接访问全局本地存储变量。可以轻松地在包装器中设置模拟以进行测试。

      【讨论】:

        【解决方案6】:

        如果使用 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

        【讨论】:

        • 嗨 c4k!您能否举例说明您将如何在测试中使用它?
        • 什么意思?您不必在测试中初始化任何内容,它只会自动模拟您在代码中使用的localStorage。 (如果你使用create-react-app 以及它自然提供的所有自动脚本)
        • 如果你想确保它被调用,你可以通过在你的测试中执行类似expect(localStorage.getItem).toBeCalledWith('token')expect(localStorage.getItem.mock.calls.length).toBe(1)的操作来测试你的localStorageMock的函数是否被使用。查看facebook.github.io/jest/docs/en/mock-functions.html
        • 为此我收到一个错误 - jest.fn() 值必须是模拟函数或间谍。有什么想法吗?
        • 如果您有多个使用localStorage 的测试,这不会导致问题吗?您不想在每次测试后重置间谍以防止“溢出”到其他测试吗?
        【解决方案7】:

        正如@ck4 建议的那样,documentation 对在玩笑中使用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/mocks/setUpStore.js

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

        片段引用自here

        【讨论】:

          【解决方案8】:

          目前(2019 年 10 月)localStorage 不能像往常一样被 jest 模拟或监视,正如 create-react-app 文档中所述。这是由于在 jsdom 中所做的更改。您可以在 jestjsdom 问题跟踪器中了解它。

          作为一种解决方法,您可以改为监视原型:

          // 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 就可以了,不需要重写 setItem 函数jest.spyOn(window.localStorage.__proto__, 'setItem');
          • 是的,我将两者列为备选方案,无需同时进行。
          • 我的意思是不覆盖 setItem 以及 ?
          • 啊,是的。我是说你可以使用第一行或第二行。它们是做同样事情的替代品。无论您的个人喜好是什么 :) 很抱歉造成混乱。
          • FWIW,eslint 现在说 obj.__proto__ 已被弃用,最好使用 Object.getPrototypeOf(obj) 代替。这似乎也适用于此。
          【解决方案9】:

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

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

          【讨论】:

            【解决方案10】:

            以下解决方案兼容使用更严格的 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

            【讨论】:

              【解决方案11】:
              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 对象

              【讨论】:

                【解决方案12】:

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

                我在一个项目中使用 typesctipt 对其进行了测试。

                【讨论】:

                • 对我来说 Object.defineProperty 成功了。直接对象分配不起作用。谢谢!
                • 如何处理从localStorage获取数据的服务?
                • 您应该使用模拟数据而不是服务进行测试。在单元测试中,您应该测试单个功能。
                【解决方案13】:

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

                或者你在你想要模拟本地存储的测试用例中导入这个文件。

                【讨论】:

                  【解决方案14】:

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

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

                  【讨论】:

                  【解决方案15】:

                  你需要用这个 sn-ps 模拟本地存储

                  // 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"]
                  

                  随便问什么。

                  【讨论】:

                    【解决方案16】:

                    很遗憾,我在这里找到的解决方案对我不起作用。

                    所以我在查看 Jest GitHub 问题时发现了这个 thread

                    最受好评的解决方案是:

                    const spy = jest.spyOn(Storage.prototype, 'setItem');
                    
                    // or
                    
                    Storage.prototype.getItem = jest.fn(() => 'bla');
                    

                    【讨论】:

                    • 我的测试也没有定义 windowStorage。也许这是我使用的旧版本的 Jest。
                    • 可能,它应该与最近的 jest-jsdom 版本一起出现。
                    【解决方案17】:

                    这对我有用,

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

                    【讨论】:

                      【解决方案18】:

                      我想我会在 TypeScript w/ React 中添加另一个非常适合我的解决方案:

                      我创建了一个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);
                      }
                      

                      【讨论】:

                      • 如何在这个解决方案中使用setItemMock?我有点困惑,而且对开玩笑的测试/测试也很陌生。
                      • @vveil 我还添加了一个使用 setItemMock 的示例。
                      • 非常感谢!这很有帮助。
                      • 这对我有用,谢谢!
                      【解决方案19】:

                      使用 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)
                            })
                          })
                      

                      【讨论】:

                        【解决方案20】:

                        2021,打字稿

                        class LocalStorageMock {
                          store: { [k: string]: string };
                          length: number;
                        
                          constructor() {
                            this.store = {};
                            this.length = 0;
                          }
                        
                          /**
                           * @see https://developer.mozilla.org/en-US/docs/Web/API/Storage/key
                           * @returns
                           */
                          key = (idx: number): string => {
                            const values = Object.values(this.store);
                            return values[idx];
                          };
                        
                          clear() {
                            this.store = {};
                          }
                        
                          getItem(key: string) {
                            return this.store[key] || null;
                          }
                        
                          setItem(key: string, value: string) {
                            this.store[key] = String(value);
                          }
                        
                          removeItem(key: string) {
                            delete this.store[key];
                          }
                        }
                        
                        export default LocalStorageMock;
                        

                        然后你可以使用它

                        global.localStorage = new LocalStorageMock();
                        

                        【讨论】:

                          【解决方案21】:

                          以上答案都不适合我。因此,经过一番挖掘,这就是我要工作的地方。归功于一些来源和其他答案。

                          我的全部要点:https://gist.github.com/ar-to/01fa07f2c03e7c1b2cfe6b8c612d4c6b

                          /**
                           * Build Local Storage object
                           * @see https://www.codeblocq.com/2021/01/Jest-Mock-Local-Storage/ for source
                           * @see https://stackoverflow.com/a/32911774/9270352 for source
                           * @returns
                           */
                          export const fakeLocalStorage = () => {
                            let store: { [key: string]: string } = {}
                          
                            return {
                              getItem: function (key: string) {
                                return store[key] || null
                              },
                              setItem: function (key: string, value: string) {
                                store[key] = value.toString()
                              },
                              removeItem: function (key: string) {
                                delete store[key]
                              },
                              clear: function () {
                                store = {}
                              },
                            }
                          }
                          
                          /**
                           * Mock window properties for testing
                           * @see https://gist.github.com/mayank23/7b994385eb030f1efb7075c4f1f6ac4c for source
                           * @see https://github.com/facebook/jest/issues/6798#issuecomment-514266034 for sample implementation
                           * @see https://developer.mozilla.org/en-US/docs/Web/API/Window#properties for window properties
                           * @param { string } property window property string but set to any due to some warnings
                           * @param { Object } value for property
                           *
                           * @example
                           *
                           *  const testLS = {
                           *    id: 5,
                           *    name: 'My Test',
                           *  }
                           * mockWindowProperty('localStorage', fakeLocalStorage())
                           * window.localStorage.setItem('currentPage', JSON.stringify(testLS))
                           *
                           */
                          const mockWindowProperty = (property: string | any, value: any) => {
                            const { [property]: originalProperty } = window
                            delete window[property]
                            beforeAll(() => {
                              Object.defineProperty(window, property, {
                                configurable: true,
                                writable: true,
                                value,
                              })
                            })
                            afterAll(() => {
                              window[property] = originalProperty
                            })
                          }
                          
                          export default mockWindowProperty
                          
                          

                          【讨论】:

                            【解决方案22】:

                            正如a comment Niket Pathak 所述, 从 jest@24 / jsdom@11.11.0 及以上开始,localStorage 会自动模拟。

                            【讨论】:

                              【解决方案23】:
                              Object.defineProperty(window, "localStorage", {
                                value: {
                                  getItem: jest.fn(),
                                  setItem: jest.fn(),
                                  removeItem: jest.fn(),
                                },
                              });
                              

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

                              【讨论】:

                                猜你喜欢
                                • 2020-10-23
                                • 2022-01-13
                                • 1970-01-01
                                • 2020-06-30
                                • 1970-01-01
                                • 2022-01-21
                                • 1970-01-01
                                • 2021-06-05
                                • 2021-08-04
                                相关资源
                                最近更新 更多