Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mocking localstorage #2098

Closed
DarrylD opened this issue Nov 15, 2016 · 23 comments
Closed

Mocking localstorage #2098

DarrylD opened this issue Nov 15, 2016 · 23 comments

Comments

@DarrylD
Copy link

DarrylD commented Nov 15, 2016

Getting localStorage is not defined. Is it a particular way I should go about mocking browser api?

@DarrylD
Copy link
Author

DarrylD commented Nov 15, 2016

Found a the solution

//browserMocks.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
});

And in the jest config: "setupFiles":["browserMocks.js"]

@rgbkrk
Copy link

rgbkrk commented Feb 13, 2017

Is there a way to do this that relies on a module directly rather than a local file? I want to use a common configuration for document that has document.createRange and some others defined, across a many-component lerna monorepo.

@rgbkrk
Copy link

rgbkrk commented Feb 13, 2017

Found out that setupFiles can take a module name. In our monorepo, I set our configuration up as:

"jest": {
  "setupFiles": ["@nteract/mockument"]
},

and it loaded that new lerna package properly. 💯

@tarim
Copy link

tarim commented May 12, 2017

It doesn't work for me. It said window is not defined.

any idea please?

@thymikee
Copy link
Collaborator

@tarim you're probably having testEnvironment set to node, but you should use jsdom. Please see the docs: http://facebook.github.io/jest/docs/configuration.html#testenvironment-string

@tarim
Copy link

tarim commented May 12, 2017

@thymikee, Thank you very much.

@efalayi
Copy link

efalayi commented May 29, 2017

It worked! Thanks

@jariztia
Copy link

jariztia commented Feb 9, 2018

How can I test a localStorage failure to test the catch path?

@guilhermebruzzi
Copy link

guilhermebruzzi commented Feb 28, 2018

Example including removeItem and defining without var (I needed this way, may help others searching):

// browser mocks
const localStorageMock = (function() {
  let store = {}
  return {
    getItem: function(key) {
      return store[key] || null
    },
    setItem: function(key, value) {
      store[key] = value.toString()
    },
    removeItem: function(key) {
      delete store[key]
    },
    clear: function() {
      store = {}
    },
  }
})()

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

@sonaye
Copy link

sonaye commented Jun 6, 2018

My take on this:

// localStorage.js
export default new class {
  store = {};
  setItem = (key, val) => (this.store[key] = val);
  getItem = key => this.store[key];
  removeItem = key => { delete this.store[key]; };
  clear = () => (this.store = {});
}();

Usage:

// foo.test.js
import localStorage from './localStorage';

window.localStorage = localStorage;

// ..

Plus some tests for it:

// localStorage.test.js
import localStorage from './localStorage';

describe('localStorage', () => {
  beforeEach(() => localStorage.clear());

  it('is initialized properly', () => expect(localStorage.store).toEqual({}));

  it("returns undefined if requested item doesn't exist", () => {
    const foo = localStorage.getItem('foo');
    expect(foo).toBeUndefined();
  });

  it('sets the value of an item', () => {
    localStorage.setItem('foo', 'bar');
    expect(localStorage.store).toEqual({ foo: 'bar' });
  });

  it('gets the value of an item', () => {
    localStorage.setItem('foo', 'bar');
    const foo = localStorage.getItem('foo');
    expect(foo).toBe('bar');
  });

  it('removes an item', () => {
    localStorage.setItem('foo', 'bar');
    localStorage.removeItem('foo');
    const foo = localStorage.getItem('foo');
    expect(foo).toBeUndefined();
  });

  it('clears all items', () => {
    localStorage.setItem('foo', 'qwerty');
    localStorage.setItem('bar', 'asdf');
    expect(localStorage.store).toEqual({ foo: 'qwerty', bar: 'asdf' });
    localStorage.clear();
    expect(localStorage.store).toEqual({});
  });
});

@SimenB
Copy link
Member

SimenB commented Jun 7, 2018

There's also https://www.npmjs.com/package/jest-localstorage-mock

@matiasmenker
Copy link

With ES6:

`const localStorageMock = (function () {
let store = {};

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

}());

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

@kaufmann42
Copy link

Added in this commit

@SimenB
Copy link
Member

SimenB commented Jul 3, 2018

Oh, that's awesome! It'll probably be within semver range of Jest 22 and 23, then.

@tripper54
Copy link

If you're having trouble re-assigning mock storage between tests, try

Object.defineProperty(window, 'localStorage', {
      value: localStorageMock,
      writable: true
    })

@remyba
Copy link

remyba commented Jun 21, 2019

This works when localStorage is being called in the test file, but how can one mock localStorage when it is being used by a service that the test file is calling/testing?

@kaufmann42
Copy link

@remyba You should be able to define it as a mock and get creative with Jest global properties. Maybe something like this answer could help.

@Rokt33r
Copy link

Rokt33r commented Sep 7, 2019

#2098 (comment) 's getItem looks wrong.

return store[key] || null

If store[key] is empty string '', it will return null. So please don't abuse || otherwise you will get troubles.

So getItem should be like

getItem: function(key) {
  return store[key] != null ? store[key] : null
}

@Wolven531
Copy link

Wolven531 commented Sep 14, 2019

Drawing inspiration from @tripper54 , my test setup looks like this:

describe('altering window.localStorage', () => {
	let originalLocalStorage: Storage

	beforeEach(() => {
		originalLocalStorage = window.localStorage
	})

	afterEach(() => {
		(window as any).localStorage = originalLocalStorage
	})

	describe('loadFromStorage when localStorage is unavailable', () => {
		beforeEach(() => {
			(window as any).localStorage = undefined
			fixture.loadFromStorage()
		})

		it('should not affect critters', () => {
			expect(fixture.critters).toEqual([new CritterModel('critter 1', 10, 1, 0, 'id1')])
		})
	})

	describe('loadFromStorage when localStorage.getItem returns null', () => {
		beforeEach(() => {
			Object.defineProperty(window, 'localStorage', {
				value: {
					getItem: jest.fn(() => null)
				},
				writable: true
			})
			fixture.loadFromStorage()
		})

		it('should not affect critters', () => {
			expect(fixture.critters).toEqual([new CritterModel('critter 1', 10, 1, 0, 'id1')])
		})
	})
})

In the final beforeEach block, you can replace null w/ any value you'd like

For example:

jest.fn(() => JSON.stringify( [] ))
jest.fn(() => JSON.stringify( [{ someProp: 'someVal', someOtherProp: 5 }] ))

Very simple setup and works flawlessly. Thanks for the advice, all. ❤

@LuckyExpress
Copy link

It doesn't work for something like Object.keys(localStorage)

@vse-volod
Copy link

vse-volod commented Feb 22, 2021

I'm using this hook:

const mockWindowProperty = (property, value) => {
  const { [property]: originalProperty } = window;
  delete window[property];
  beforeAll(() => {
    Object.defineProperty(window, property, {
      configurable: true,
      writable: true,
      value,
    });
  });
  afterAll(() => {
    window[property] = originalProperty;
  });
};

describe('localStorage test', () => {
  mockWindowProperty('localStorage', {
    setItem: jest.fn(),
    getItem: jest.fn(),
    removeItem: jest.fn(),
  });
  it('localStorage mock works', () => {
    window.localStorage.setItem('abc');
    expect(window.localStorage.setItem).toHaveBeenCalledWith('abc');
  });
});

@CoryDanielson
Copy link

CoryDanielson commented Apr 23, 2021

I made some additions to #2098 (comment) and addressed #2098 (comment)

It doesn't work for something like Object.keys(localStorage)

I added support for Object.keys(localStorage) by saving properties onto the object itself, rather than nested inside of this.store. I also added a length property.

class MockLocalStorage {
	getItem(key) {
		return this[key] || null;
	}
	setItem(key, value) {
		this[key] = value.toString();
	}
	removeItem(key) {
		delete this[key];
	}
	clear() {
		Object.keys(this).forEach(k => this.removeItem(k));
	}
	get length() {
		return Object.keys(this).length;
	}
}

An edge case that this does not support, is setting the localStorage values with keys that have the same names as the methods in the class itself... ie, localStorage.setItem('setItem', 'test') will override the setItem method and break functionality.

@github-actions
Copy link

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 27, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests