0

My jest.Spyon implementation seems pretty straight forward, but the spied function "does not" get called even though I see that it must be getting called.

Function

/**
 * Initialize the local & sync storage when the user first installs TabMerger.
 * @param {{blacklist: string, color: string, dark: boolean,
 *  open: "with" | "without", restore: "keep" | "remove", title: string}} default_settings TabMerger's original default settings
 * @param {{color: string, created: string, tabs: object[], title: string}} default_group TabMerger's original default group (with up-to-date timestamp)
 * @param {HTMLElement} sync_node Node indicating the "Last Sync" time
 * @param {Function} setGroups For re-rendering the initial groups
 * @param {Function} setTabTotal For re-rendering the total tab counter
 *
 * @see defaultSettings in App.js
 * @see defaultGroup in App.js
 */
// prettier-ignore
export function storageInit(default_settings, default_group, sync_node, setGroups, setTabTotal) {
  chrome.storage.sync.get(null, (sync) => {
    if (!sync.settings) {
      chrome.storage.sync.set({ settings: default_settings }, () => {});
      toggleDarkMode(true);
    } else {
      toggleDarkMode(sync.settings.dark);
    }
 
    if (sync["group-0"]) {
      toggleSyncTimestampHelper(true, sync_node);
    }
 
    delete sync.settings;
    chrome.storage.local.get("groups", (local) => {
      var ls_entry = local.groups || { "group-0": default_group };
 
      chrome.storage.local.remove(["groups"], () => {
        chrome.storage.local.set({ groups: ls_entry }, () => {
          setGroups(JSON.stringify(ls_entry));
          updateTabTotal(ls_entry, setTabTotal);
        });
      });
    });
  });
}

Test

import * as AppFunc from "../src/App/App_functions";

/* 
{
  AppFunc:
  {
    toggleDarkMode: [Function: toggleDarkMode],
    ...
    toggleSyncTimestampHelper: [Function: toggleSyncTimestampHelper],
    storageInit: [Function: storageInit],
    ...
  }
}
*/

...

test("sync group-0 exists", () => {
  const spy = jest.spyOn(AppFunc, "toggleSyncTimestampHelper");
  sessionStorage.setItem("group-0", 1);

  // prettier-ignore
  AppFunc.storageInit(default_settings, default_group, sync_node, mockSet, mockSet);

  expect(spy).toHaveBeenCalledTimes(1);
  expect(spy).toHaveBeenCalledWith(true, sync_node);
});

Coverage Report

coverage report As can be seen in line 197, it is clearly called once.

Error

Error snapshot

This is very confusing because my tests successfully spyOn chrome.storage.___.___ API using jest.spyOn(chrome.storage.local, "get") but this fails.

If it helps, my repository is open source: https://github.com/lbragile/TabMerger, but this latest test is not included there yet.

lbragile
  • 7,549
  • 3
  • 27
  • 64
  • You shouldn't mock functions on module object because it's read-only by specs. And you can't mock a function that is used in the same module it's defined. You omitted that toggleSyncTimestampHelper and storageInit are both in App_functions. Split it. – Estus Flask Jan 02 '21 at 21:59
  • Really? There is no way to spyOn functions inside the component under test? Very strange. So my helper functions need to be in a separate file completely. Got it – lbragile Jan 03 '21 at 07:03
  • This is how JS works, not specific to modules or Jest, toggleSyncTimestampHelper variable is referred in local scope (module scope), and local scope cannot be accessed from the outside (another module). This could be done with Rewire, but it's a hack and it's incompatible with Jest. – Estus Flask Jan 03 '21 at 08:18
  • Yes, after further analysis about what you said, I can now see the full picture. It is more logical to move all the helper functions to a separate file and have additional tests for them. This way you could also spy on them when testing the main functions. The reason you cannot spy on helper functions (as they currently are used in my case) is because they are “local” to the module itself. Thus moving them to another file will no longer make them local. – lbragile Jan 03 '21 at 17:40

0 Answers0