skip to Main Content

Going around in circles at the moment and hoping someone can help clear something up please

Im trying to get my head around unit testing window.localStorage and believe im almost there, I just cant seem to access the actual key/value pairs when testing my final helper method

I have the following methods

export function testSet() {
  window.localStorage.setItem('testSet', '1234');
}

export function testDeletePrefix() {
  let deleted = false;
  Object.entries(window.localStorage).forEach(([key, value]) => {
  console.log(key);
  // This logs out getItem setItem removeItem clear
  if (key.startsWith("test") && key !== "test") {
    window.localStorage.removeItem(key);
    console.log(`Item with key '${key}' deleted from local storage.`);
    deleted = true;
  }
});
  if (!deleted) {
    console.log(`No key starting with 'test' found in local storage.`);
  }
}

Unit test

setItemSpy = spyOn(window.localStorage, "setItem").and.callFake(
    (key, value) => {
      store[key] = value.toString();
    }
  );

testSet();
expect(setItemSpy).toHaveBeenCalledWith("testSet", "1234");
testDeletePrefix();

Whenever i run this test i get the output No key starting with 'test' found in local storage.

So the spyOn has confirmed the key/value pair has been set, so why can’t I access them when looping over window.localStorage ? Would really appreciate some pointers so i can understand this

2

Answers


  1. Chosen as BEST ANSWER

    Im going to post a solution that is now working for me (though if anyone spots something that's not right, please let me know)

    I started with creating a mockLocalStorage helper

    export const mockLocalStorage = {
      data: {},
        setItem: function(key, value) {
          this.data[key] = value;
        },
        getItem: function(key) {
          return this.data[key];
        },
        removeItem: function(key) {
          delete this.data[key];
        },
        clear: function() {
          this.data = {};
        },
        entries: function() {
          return Object.entries(this.data);
        },
      };
    

    The in my test spec i have

    import { testDeletePrefix } from "../src/my_helper.js";
    import { mockLocalStorage } from "./support/mockLocalStorage.js";
    
    describe("deletePrefix", () => {
    
      beforeEach(() => {
        mockLocalStorage.data = {};
      });
    
      it("should delete a prefixed item from local storage if it exists", () => {
        // Set the item
        mockLocalStorage.setItem('test123', 'test');
    
        spyOn(mockLocalStorage, 'removeItem').and.callThrough();
        // Call testDeletePrefix function, passing in localStorage of choice
        testDeletePrefix(mockLocalStorage);
    
        // Expectations
        expect(mockLocalStorage.removeItem).toHaveBeenCalledTimes(1);
        expect(mockLocalStorage.getItem('test123')).toBeUndefined();
      });
    });
    

    Finally I updated the testDeletePrefix() method to accept an argument, which is the localStorage object, so in test it can be mockLocalStorage and when live a user can pass in window.localStorage

    export function testDeletePrefix(localStorage) {
      let deleted = false;
      const entries = localStorage.entries ? localStorage.entries() : Object.entries(localStorage);
      entries.forEach(([key, value]) => {
        if (key.startsWith('test) && key !== 'test) {
          localStorage.removeItem(key);
          deleted = true;
        }
      });
      if (!deleted) {
        console.log(`No key starting with test followed by text found in local storage.`);
      }
    }
    

  2. The issue is the .and.callFake(. We are saying that for every time you are called, call this fake implementation and not the real implementation. We need the real implementation for testDeletePrefix to have the item in local storage.

    Try using .and.callThrough() to call the actual implementation.

    // !! Change this to .and.callThrough() to call the actual implementation
    // every time `localStorage.setItem` is called and then the actual implementation
    // of localStorage.setItem is called.
    setItemSpy = spyOn(window.localStorage, "setItem").and.callThrough();
    
    testSet();
    expect(setItemSpy).toHaveBeenCalledWith("testSet", "1234");
    testDeletePrefix();
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search