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


144

我在Jest测试中一直收到“未定义localStorage”的说法,这很有意义,但是我有什么选择呢?打砖墙。

Answers:


141

来自的绝佳解决方案 @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;

8
应该应该value + ''在setter中正确地处理null和undefined值
menehune23 '17

我认为最新的玩笑只是在使用它,|| null这就是为什么我的测试失败了,因为在我的测试中我正在使用not.toBeDefined()。@Chiedo解决方案使其再次起作用
jcubic

我想,这在技术上是一个存根:)在这里看到的嘲笑版:stackoverflow.com/questions/32911630/...
TigerBear

100

在以下帮助下找到了答案: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",


6
显然,其中一项更新更改了此参数的名称,现在将其称为“ setupTestFrameworkScriptFile”
Grzegorz Pawlik

2
"setupFiles": [...]也可以。使用array选项,可以将模拟文件分离到单独的文件中。例如:"setupFiles": ["<rootDir>/__mocks__/localStorageMock.js"]
Stiggler

3
getItem如果未针对特定键设置任何数据,则返回值与浏览器返回的值略有不同。getItem("foo")例如,当未设置时调用会null在浏览器中返回,但是undefined通过这种模拟-这导致我的测试之一失败。对我来说,简单的解决方案是返回store[key] || nullgetItem函数
Ben Broadley,2017年

如果您执行类似操作,则此操作不起作用localStorage['test'] = '123'; localStorage.getItem('test')
抢劫

3
我收到以下错误-jest.fn()值必须是模拟函数或间谍。有任何想法吗?
保罗·菲茨杰拉德'18

55

如果使用create-react-app,则文档中介绍了一个更简单明了的解决方案。

创建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!您能否举个例子,说明如何在测试中使用它?
Dimo

你什么意思 ?您不必在测试中初始化任何东西,它只是自动模拟localStorage您在代码中使用的内容。(如果您使用create-react-app它自然提供的所有自动脚本)
c4k

然后,如果要确保已调用localStorageMock的功能,可以通过在测试中进行类似expect(localStorage.getItem).toBeCalledWith('token')expect(localStorage.getItem.mock.calls.length).toBe(1)内部的操作来测试其是否已使用。看看facebook.github.io/jest/docs/en/mock-functions.html
汤姆·默兹

10
为此,我遇到一个错误-jest.fn()值必须是模拟函数或间谍。有任何想法吗?
保罗·菲茨杰拉德'18

3
如果您使用多个测试,这不会引起问题localStorage吗?您是否不想在每次测试后重置间谍以防止“溢出”到其他测试中?
布兰登·斯特金

43

当前(19年10月)不能像平常那样,以开玩笑的方式嘲笑或监视localStorage,正如create-react-app文档中所述。这是由于jsdom中所做的更改。您可以在玩笑jsdom中阅读有关它的信息问题跟踪器中。

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

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

是的,我列出了这两种方法,不需要两者都做。
巴斯蒂安·斯坦

我的意思没有setItem的覆盖以及😉
勒芒Dahmani

我不明白。你能澄清一下吗?
巴斯蒂安·斯坦

1
是的。我说的是您可以使用第一行或第二行。它们是做相同事情的替代方案。无论您的个人喜好是什么:)抱歉让您感到困惑。
巴斯蒂安·斯坦


13

一个更好的替代方法,可以处理undefined值(不包含toString()),并null在值不存在时返回。用reactv15进行了测试,redux并且redux-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

感谢Alexis Tyler提出的添加想法removeItemdeveloper.mozilla.org/en-US/docs/Web/API/Storage/removeItem
Dmitriy

相信null和undefined需要导致“ null”和“ undefined”(文字字符串)
menehune23

6

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

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

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

5

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

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


对我来说,Object.defineProperty可以解决问题。直接对象分配无效。谢谢!
Vicens Fayos,

4

不幸的是,我在这里找到的解决方案不适用于我。

所以我看着Jest GitHub问题,发现了这个线程

最受欢迎的解决方案是:

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

// or

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

我的测试也没有window或没有Storage定义。也许是我使用的Jest的旧版本。
Antrikshy

3

正如@ 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/mocks/setUpStore.js

let mockStorage = {};

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

此处引用代码段


2

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


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


2

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

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

2

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

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

随便问什么。


1

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


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

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


0

这对我有用

delete global.localStorage;
global.localStorage = {
getItem: () => 
 }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.