DEV Community

Yuki Shindo
Yuki Shindo

Posted on

A mock of localStorage written in TypeScript

A mock of localStorage written in TypeScript

The other day, I started writing mock of localStorage in TypeScript.
Here is the code that I actually wrote.

Regarding this code, please note that it does not behave completely the same as localStorage.

The behavior is not perfectly reproduced (The value returned when an unintended value is passed, for example), but the basic logic is implemented.

type Store = any;

class LocalStorageMock {
  store: Store;
  length: number;

  constructor() {
    this.store = {};
    this.length = 0;
  }

  key(n: number): any {
    if (typeof n === 'undefined') {
      throw new Error(
        "Uncaught TypeError: Failed to execute 'key' on 'Storage': 1 argument required, but only 0 present."
      );
    }

    if (n >= Object.keys(this.store).length) {
      return null;
    }

    return Object.keys(this.store)[n];
  }

  getItem(key: string): Store | null {
    if (!Object.keys(this.store).includes(key)) {
      return null;
    }

    return this.store[key];
  }

  setItem(key: string, value: any): undefined {
    if (typeof key === 'undefined' && typeof value === 'undefined') {
      throw new Error(
        "Uncaught TypeError: Failed to execute 'setItem' on 'Storage': 2 arguments required, but only 0 present."
      );
    }

    if (typeof value === 'undefined') {
      throw new Error(
        "Uncaught TypeError: Failed to execute 'setItem' on 'Storage': 2 arguments required, but only 1 present."
      );
    }

    if (!key) return undefined;

    this.store[key] = value.toString() || '';
    this.length = Object.keys(this.store).length;

    return undefined;
  }

  removeItem(key: string): undefined {
    if (typeof key === 'undefined') {
      throw new Error(
        "Uncaught TypeError: Failed to execute 'removeItem' on 'Storage': 1 argument required, but only 0 present."
      );
    }

    delete this.store[key];
    this.length = Object.keys(this.store).length;
    return undefined;
  }

  clear(): undefined {
    this.store = {};
    this.length = 0;

    return undefined;
  }
}

export const getLocalStorageMock = (): any => {
  return new LocalStorageMock();
};
Enter fullscreen mode Exit fullscreen mode

About localStorage behavior

I actually investigated the behavior of localStorage in the browser before writing this code.
I noticed that it behaved in a slightly interesting way.

For example, the following code.
(I didn't do an exhaustive check, so there may be other unique behaviors hidden in the code)

Note that the environment has been tested with Chrome 94.0.4606.81 (Official Build) (arm64).
I haven't tried it with other browsers, so the behavior might be different with different browsers.

window.localStorage.setItem('test', 'aaa')

window.localStorage.key(0)
// => 'test'

window.localStorage.key('a')
// => 'test'

window.localStorage.key(false)
// => 'test'

window.localStorage.key(undefined)
// => 'test'

window.localStorage.key(null)
// => 'test'
Enter fullscreen mode Exit fullscreen mode

Note that if you do not pass any argument to the key function, you will get an error.

window.localStorage.key()
// => Uncaught TypeError: Failed to execute 'key' on 'Storage': 1 argument required, but only 0 present.
Enter fullscreen mode Exit fullscreen mode

Here is the MDN documentation on the key function.
https://developer.mozilla.org/ja/docs/Web/API/Storage/key

A mock of localStorage is available on npm

The localStorage mock I wrote at the beginning of this post, written in TypeScript, is available on npm.

@shinshin86/local-storage-mock

localStorage Mock(GitHub)

Actually, it is helping me write tests for localStorage in another project of mine.
If anyone is considering using it for a similar purpose, please give it a try.

Installation is done like this.

npm install @shinshin86/local-storage-mock
# or
yarn add @shinshin86/local-storage-mock
Enter fullscreen mode Exit fullscreen mode

It can be made to work in this way.

const { getLocalStorageMock } = require('local-storage-mock');

const window = {
  localStorage: getLocalStorageMock(),
};

window.localStorage.setItem('testkey', 'testvalue');
console.log(window.localStorage.getItem('testkey'));
// => testvalue

console.log(window.localStorage.key(0));
// => testkey

console.log(window.localStorage.length);
// => 1

window.localStorage.removeItem('testkey');
console.log(window.localStorage.getItem('testkey'));
// => null

window.localStorage.setItem('testkey', 'testvalue');
console.log(window.localStorage.getItem('testkey'));
// => testvalue

window.localStorage.clear();
console.log(window.localStorage.length);
// => 0

console.log(window.localStorage.getItem('testkey'));
// => null
Enter fullscreen mode Exit fullscreen mode

Thanks for reading to the end!

Discussion (0)