【问题标题】:Testing Cross Browser Extension With Jest, How To Mock Chrome Storage API?用 Jest 测试跨浏览器扩展,如何模拟 Chrome 存储 API?
【发布时间】:2020-12-30 06:53:51
【问题描述】:

由于赛普拉斯不允许访问 chrome:// 网址而推迟了一段时间的测试后,我决定最终了解如何对我的扩展 - TabMerger 进行单元/集成测试。这是在我多次手动测试不断增长的功能并且在某些情况下忘记检查一两件事之后发生的。进行自动化测试肯定会加快这个过程,并帮助我在添加新功能时更加平静。

为此,我选择了 Jest,因为我的扩展是使用 React (CRA) 制作的。我还使用 React 测试库 (@testing-library/react) 来渲染所有 React 组件以进行测试。

由于我最近将 TabMerger 开源,可以在 here 找到完整的测试脚本

这是我想针对这个问题关注的测试用例:

import React from "react";
import { render, fireEvent } from "@testing-library/react";

import * as TabFunc from "../src/Tab/Tab_functions";

import Tab from "../src/Tab/Tab";

var init_groups = {
  "group-0": {
    color: "#d6ffe0",
    created: "11/12/2020 @ 22:13:24",
    tabs: [
      {
        title:
          "Stack Overflow - Where Developers Learn, Share, & Build Careersaaaaaaaaaaaaaaaaaaaaaa",
        url: "https://stackoverflow.com/",
      },
      {
        title: "lichess.org • Free Online Chess",
        url: "https://lichess.org/",
      },
      {
        title: "Chess.com - Play Chess Online - Free Games",
        url: "https://www.chess.com/",
      },
    ],
    title: "Chess",
  },
  "group-1": {
    color: "#c7eeff",
    created: "11/12/2020 @ 22:15:11",
    tabs: [
      {
        title: "Twitch",
        url: "https://www.twitch.tv/",
      },
      {
        title: "reddit: the front page of the internet",
        url: "https://www.reddit.com/",
      },
    ],
    title: "Social",
  },
};

describe("removeTab", () => {
  it("correctly adjusts groups and counts when a tab is removed", () => {
    var tabs = init_groups["group-0"].tabs;
    const { container } = render(<Tab init_tabs={tabs} />);
    expect(container.getElementsByClassName("draggable").length).toEqual(3);

    var removeTabSpy = jest.spyOn(TabFunc, "removeTab");

    fireEvent.click(container.querySelector(".close-tab"));
    expect(removeTabSpy).toHaveBeenCalledTimes(1);
    expect(container.getElementsByClassName("draggable").length).toEqual(2); // fails (does not remove the tab for some reason)
  });
});

我根据自己的需要 mock 了 Chrome API,但是觉得少了点什么。为了模拟 Chrome API,我关注了这篇文章(以及其他许多人,甚至对于其他测试运行者,如 Jasmine):testing chrome.storage.local.set with jest

即使 Chrome 存储 API 被嘲笑,我认为问题在于这个函数,它在初始渲染时被调用。也就是说,我认为chrome.storage.local.get 实际上并没有被执行,但不知道为什么。

// ./src/Tab/Tab_functions.js
/**
 * Sets the initial tabs based on Chrome's local storage upon initial render.
 * If Chrome's local storage is empty, this is set to an empty array.
 * @param {function} setTabs For re-rendering the group's tabs
 * @param {string} id Used to get the correct group tabs
 */
export function setInitTabs(setTabs, id) {
  chrome.storage.local.get("groups", (local) => {
    var groups = local.groups;
    setTabs((groups && groups[id] && groups[id].tabs) || []);
  });
}

我认为模拟的 Chrome 存储 API 无法正常工作的原因是,当我在测试中手动设置它时,选项卡的数量不会从 0 增加。这迫使我将道具 (props.init_tabs) 传递给我的 Tab 用于测试目的的组件 (https://github.com/lbragile/TabMerger/blob/f78a2694786d11e8270454521f92e679d182b577/src/Tab/Tab.js#L33-L35) - 如果可能的话,我想通过设置本地存储来避免这种情况。

有人能指出我正确的方向吗?我想避免使用像 jest-chrome 这样的库,因为它们抽象太多,让我更难理解测试中发生了什么。

【问题讨论】:

    标签: javascript reactjs unit-testing jestjs mocking


    【解决方案1】:

    我想我现在有一个解决方案,所以我会与其他人分享。

    我为我的 chrome 存储 API 进行了适当的模拟以使用 localStorage:

    // __mocks__/chromeMock.js
    ...
    storage: {
        local: {
          ...,
          get: function (key, cb) {
            const item = JSON.parse(localStorage.getItem(key));
            cb({ [key]: item });
          },
          ...,
          set: function (obj, cb) {
            const key = Object.keys(obj)[0];
            localStorage.setItem(key, JSON.stringify(obj[key]));
            cb();
          },
        },
        ...
    },
    ...
    

    另外,为了模拟初始渲染时的选项卡设置,我有一个 beforeEach 钩子,它使用上面的模拟设置我的 localStorage

    // __tests__/Tab.spec.js
    var init_ls_entry, init_tabs, mockSet;
    
    beforeEach(() => {
      chrome.storage.local.set({ groups: init_groups }, () => {});
      init_ls_entry = JSON.parse(localStorage.getItem("groups"));
      init_tabs = init_ls_entry["group-0"].tabs;
      mockSet = jest.fn(); // mock for setState hooks
    });
    

    AND 最重要的是,当我 render(&lt;Tab/&gt;) 时,我注意到我没有提供 id 道具,它不会导致任何渲染(就 localStorage 的标签而言),所以现在我有了这个:

    // __tests__/Tab.spec.js
    describe("removeTab", () => {
      it("correctly adjusts storage when a tab is removed", async () => {
        const { container } = render(
          <Tab id="group-0" setTabTotal={mockSet} setGroups={mockSet} />
        );
    
        var removeTabSpy = jest.spyOn(TabFunc, "removeTab");
        var chromeSetSpy = jest.spyOn(chrome.storage.local, "set");
    
        fireEvent.click(container.querySelector(".close-tab"));
    
        await waitFor(() => {
          expect(chromeSetSpy).toHaveBeenCalled();
        });
    
        chrome.storage.local.get("groups", (local) => {
          expect(init_tabs.length).toEqual(3);
          expect(local.groups["group-0"].tabs.length).toEqual(2);
          expect(removeTabSpy).toHaveBeenCalledTimes(1);
        });
    
        expect.assertions(4);
      });
    });
    

    哪个通过!!

    现在开始拖放测试?

    【讨论】:

      猜你喜欢
      • 2011-06-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-27
      • 1970-01-01
      • 1970-01-01
      • 2013-07-19
      相关资源
      最近更新 更多