what is the best way to mock window.sessionStorage in jest
Asked Answered
B

5

35

Below is a very simple jest unit test and when running it, you will get error like

Cannot spyOn on a primitive value; undefined given

TypeError: Cannot read property 'getItem' of undefined

but according to the last two comments of this post, localStorage and sessionStorage were already added to latest JSDOM and jest. If using jest-localstorage-mock and add it to my jest setupFiles then you will see weird error like

TypeError: object[methodName].mockImplementation is not a function

So my question is what's the best way to mock localStorage/sessionStorage in jest. Thanks

describe('window.sessionStorage', () => {
    let mockSessionStorage;
    beforeEach(() => {
        mockSessionStorage = {};
        jest.spyOn(window.sessionStorage, "getItem").mockImplementation(key => {
            return mockSessionStorage[key];
        });
    });

    describe('getItem-', () => {
        beforeEach(() => {
            mockSessionStorage = {
                foo: 'bar',
            }
        });

        it('gets string item', () => {
            const ret = window.sessionStorage.getItem('foo');
            expect(ret).toBe('bar');
        });
    });
});

Below is my jest config

module.exports = {
    verbose: true,
    //setupFiles: ["jest-localstorage-mock"],
    testURL: "http://localhost/"
};
Blunderbuss answered 27/7, 2018 at 23:49 Comment(0)
T
35

Here is the solution only use jestjs and typescript, nothing more.

index.ts:

export function getUserInfo() {
  const userInfo = window.sessionStorage.getItem('userInfo');
  if (userInfo) {
    return JSON.parse(userInfo);
  }
  return {};
}

index.spec.ts:

import { getUserInfo } from './';

const localStorageMock = (() => {
  let store = {};

  return {
    getItem(key) {
      return store[key] || null;
    },
    setItem(key, value) {
      store[key] = value.toString();
    },
    removeItem(key) {
      delete store[key];
    },
    clear() {
      store = {};
    }
  };
})();

Object.defineProperty(window, 'sessionStorage', {
  value: localStorageMock
});

describe('getUserInfo', () => {
  beforeEach(() => {
    window.sessionStorage.clear();
    jest.restoreAllMocks();
  });
  it('should get user info from session storage', () => {
    const getItemSpy = jest.spyOn(window.sessionStorage, 'getItem');
    window.sessionStorage.setItem('userInfo', JSON.stringify({ userId: 1, userEmail: '[email protected]' }));
    const actualValue = getUserInfo();
    expect(actualValue).toEqual({ userId: 1, userEmail: '[email protected]' });
    expect(getItemSpy).toBeCalledWith('userInfo');
  });

  it('should get empty object if no user info in session storage', () => {
    const getItemSpy = jest.spyOn(window.sessionStorage, 'getItem');
    const actualValue = getUserInfo();
    expect(actualValue).toEqual({});
    expect(window.sessionStorage.getItem).toBeCalledWith('userInfo');
    expect(getItemSpy).toBeCalledWith('userInfo');
  });
});

Unit test result with 100% coverage report:

 PASS  src/stackoverflow/51566816/index.spec.ts
  getUserInfo
    ✓ should get user info from session storage (6ms)
    ✓ should get empty object if no user info in session storage (1ms)

----------|----------|----------|----------|----------|-------------------|
File      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files |      100 |      100 |      100 |      100 |                   |
 index.ts |      100 |      100 |      100 |      100 |                   |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        4.548s, estimated 6s

Here is the completed demo: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/51566816

Transpose answered 28/9, 2019 at 3:14 Comment(4)
anything to be done to restore the original behavior of session storage at the end? Will this mock be valid only for the current test file and won't leak globally?Contributor
You probably do not even need a mock. Just use window.sessionStorage as usual and write your condition based on window.sessionStorage.getItem(...) result instead of spying window.sessionStorage.setItem . Simply don't forget to call window.sessionStorage.clear() in beforeEach as demonstrated.Maltase
@EricBurel This should be a good valid answer.Frodina
return ReferenceError: window is not definedDulsea
A
19

You probably do not even need a mock. Just use window.sessionStorage as usual and write your condition based on window.sessionStorage.getItem(...) result instead of spying window.sessionStorage.setItem . Simply don't forget to call window.sessionStorage.clear() in beforeEach as demonstrated.

From Eric Burel's comment

Amorette answered 27/7, 2018 at 23:49 Comment(0)
S
2

This worked for me in the context of using jest:

beforeAll(() =>
    sessionStorage.setItem(
        KeyStorage.KEY_NAME,
        JSON.stringify([Permission.VALUE_])
    )
);

afterAll(() => 
    sessionStorage.removeItem(KeyStorage.CONTEXT_TYPE_GLOBAL_PERMISSIONS)
);
Success answered 13/2, 2023 at 2:39 Comment(0)
A
2

While mocking a whole sessionStorage works, you might also consider this simple approach to spy on single methods, e.g. getItem():

describe('sessionStorage', () => {
  it('should call sessionStorage.getItem()', () => {
    const getItemSpy = jest.spyOn(Object.getPrototypeOf(sessionStorage), 'getItem');
    // Do something that calls `sessionStorage.getItem()`.
    expect(getItemSpy).toHaveBeenCalled();
  });
});

TypeScript version:

describe('sessionStorage', (): void => {
  it('should call sessionStorage.getItem()', (): void => {
    const getItemSpy: jest.SpyInstance = jest.spyOn(Object.getPrototypeOf(sessionStorage), 'getItem');
    // Do something that calls `sessionStorage.getItem()`.
    expect(getItemSpy).toHaveBeenCalled();
  });
});
Antho answered 14/9, 2023 at 11:22 Comment(0)
A
0

This works for me along with adding object:

defineProperty(window, 'sessionStorage', {
   writable: true,
   configurable: true,
   value: localStorageMock
}
Adnah answered 9/3, 2022 at 20:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.