在单元测试中使用Jest模拟VS Code extension API

对VS Code extension进行单元测试时通常会遇到一个问题,代码中所使用的VS Code编辑器的功能都依赖于vscode 库,但是我们在单元测试中并没有添加对vscode 库的依赖,所以导致运行单元测试时出错。由于vscode 库是作为第三方依赖被引入到我们的VS Code extension中的,所以它并不受我们的控制,最好的办法就是在单元测试中对其中的API进行模拟。本文中我将介绍如何使用Jest来模拟vscode库的API。

如果你还不太熟悉如何开始创建一个VS Code extension,这里的文档可以教你快速上手。

创建好VS Code extension项目后,你会发现在根目录下有一个package.json 文件,VS Code extension会从中读取配置项来管理UI界面元素,在实际开发中你可能会使用到其中的一些属性。我们可以通过package.json 来设置项目所需要的依赖项,这里我们将Jest添加为dev dependency,并添加npm脚本以运行Jest单元测试。

javascript 复制代码
npm i -D jest
{
  "scripts": {
    "test": "jest"
  }
}

模拟VS Code node module

Jest提供了一些mocking的选项,但是因为我们想要模拟整个vscode node module,所以最简单的办法是在与node_modules文件夹相同的位置(通常是项目的根目录)创建一个**mocks** 文件夹,并在其中添加一个与要模拟的模块名称相同的文件(vscode.js)。

你不需要在测试代码中导入该模块,mock会自动加载它。Jest称此为manual mocks

这种方法最大的好处 是它能将我们的测试代码与所依赖的模块分离,使测试代码看起来更加整洁。这里有一个小问题 ,新加入的开发者需要知道**mocks**文件夹,否则很难理解单元测试是如何正常工作的,因为单元测试中并没有VS Code模块被模拟的代码。

以下就是对VS Code模块进行模拟的代码。我们并没有模拟整个API,你可以根据需要进行调整。

javascript 复制代码
// vscode.js

const languages = {
  createDiagnosticCollection: jest.fn()
};

const StatusBarAlignment = {};

const window = {
  createStatusBarItem: jest.fn(() => ({
    show: jest.fn()
  })),
  showErrorMessage: jest.fn(),
  showWarningMessage: jest.fn(),
  createTextEditorDecorationType: jest.fn()
};

const workspace = {
  getConfiguration: jest.fn(),
  workspaceFolders: [],
  onDidSaveTextDocument: jest.fn()
};

const OverviewRulerLane = {
  Left: null
};

const Uri = {
  file: f => f,
  parse: jest.fn()
};
const Range = jest.fn();
const Diagnostic = jest.fn();
const DiagnosticSeverity = { Error: 0, Warning: 1, Information: 2, Hint: 3 };

const debug = {
  onDidTerminateDebugSession: jest.fn(),
  startDebugging: jest.fn()
};

const commands = {
  executeCommand: jest.fn()
};

const vscode = {
  languages,
  StatusBarAlignment,
  window,
  workspace,
  OverviewRulerLane,
  Uri,
  Range,
  Diagnostic,
  DiagnosticSeverity,
  debug,
  commands
};

module.exports = vscode;

使用模拟的VS Code模块的示例

我的开源项目Git Mob for VS code中使用了这种方法,我将用其中的代码来说明如何使用模拟的VS Code模块。

下面的例子中,VS Code编辑器的状态栏会根据Git钩子prepare-commit-msg 是否被调用来做相应的调整,你可以看到这里我并没有将vscode模块导入到我的测试文件中并对其进行模拟。

javascript 复制代码
// git-mob-hook-status.spec.js

const { hasPrepareCommitMsgTemplate } = require("../prepare-commit-msg-file");
const { gitMobHookStatus } = require("./git-mob-hook-status");

jest.mock("./../prepare-commit-msg-file");

describe("Hook or template status", function() {
  let mockContext;
  beforeAll(function() {
    mockContext = {
      subscriptions: []
    };
  });

  afterEach(function() {
    hasPrepareCommitMsgTemplate.mockReset();
  });

  it("using git template for co-authors", () => {
    hasPrepareCommitMsgTemplate.mockReturnValue(false);
    const statusBar = gitMobHookStatus({ context: mockContext })();
    expect(statusBar).toEqual(
      expect.objectContaining({
        text: "$(file-code) Git Mob",
        tooltip: "Using .gitmessage template"
      })
    );
  });

  it("using git prepare commit msg for co-authors", () => {
    hasPrepareCommitMsgTemplate.mockReturnValue(true);
    const statusBar = gitMobHookStatus({ context: mockContext })();
    expect(statusBar).toEqual(
      expect.objectContaining({
        text: "$(zap) Git Mob",
        tooltip: "Using prepare-commit-msg hook"
      })
    );
  });
});
javascript 复制代码
// git-mob-hook-status.js
const vscode = require("vscode");
const { hasPrepareCommitMsgTemplate } = require("../prepare-commit-msg-file");

function gitMobHookStatus({ context }) {
  const myStatusBarItem = vscode.window.createStatusBarItem(
    vscode.StatusBarAlignment.Left,
    10
  );
  context.subscriptions.push(myStatusBarItem);
  return function() {
    myStatusBarItem.text = "$(file-code) Git Mob";
    myStatusBarItem.tooltip = "Using .gitmessage template";
    if (hasPrepareCommitMsgTemplate()) {
      myStatusBarItem.text = "$(zap) Git Mob";
      myStatusBarItem.tooltip = "Using prepare-commit-msg hook";
    }
    myStatusBarItem.show();
    return myStatusBarItem;
  };
}

exports.gitMobHookStatus = gitMobHookStatus;

你可以在这里查看源代码:

我能检查vscode模块中的方法是否被调用了吗?

你可以导入模拟的vscode 模块。下面的代码中,我想要检查当用户修改co-author文件时onDidSaveTextDocument事件是否被订阅了。

javascript 复制代码
const vscode = require("../__mocks__/vscode");

// ...
test("Reload co-author list when git-coauthors file saved", () => {
  reloadOnSave(coAuthorProviderStub);
  expect(vscode.workspace.onDidSaveTextDocument).toHaveBeenCalledWith(
    expect.any(Function)
  );
  // ...
});
// ...

可以看到这里都是Jest mock API的标准用法,这意味着我们可以在代码中正常使用vscode模块的方法,而不受manual mock 的任何限制。例如,我们还可以使用mockImplementation来修改实现。

更多示例可以查看这里的源代码:

编写单元测试 最大的好处是可以快速得到反馈结果,如果你对TDD(Test-Driven Development,测试驱动开发)情有独钟,那么单元测试将使你对VS Code extension的开发更加信心满满。


资料获取方法

【留言777】

各位想获取源码等教程资料的朋友请 点赞 + 评论 + 收藏 ,三连!

三连之后我会在评论区挨个私信发给你们~

相关推荐
霍格沃兹测试开发学社测试人社区7 小时前
提升软件测试报告的质量:Allure2中添加用例失败截图、日志、HTML块和视频的方法
软件测试·测试开发
幽兰的天空8 小时前
默语博主的推荐:探索技术世界的旅程
学习·程序人生·生活·美食·交友·美女·帅哥
行則独善其身11 小时前
计算机网络-VPN虚拟专用网络概述
程序人生
行則独善其身16 小时前
华为无线AC+AP组网实际应用小结
程序人生
霍格沃兹测试开发学社测试人社区17 小时前
数据驱动与并行策略:用 JUnit 5 让软件测试更高效
软件测试·测试开发·junit
Dnelic-17 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记
霍格沃兹测试开发学社测试人社区17 小时前
软件测试丨探索 JUnit 5 中的参数化与调度执行:提升软件测试的效率与灵活性
软件测试·测试开发·junit
世间万物皆对象1 天前
Spring Boot核心概念:日志管理
java·spring boot·单元测试
AI_小站1 天前
RAG 示例:使用 langchain、Redis、llama.cpp 构建一个 kubernetes 知识库问答
人工智能·程序人生·langchain·kubernetes·llama·知识库·rag
Dnelic-1 天前
解决 Android 单元测试 No tests found for given includes:
android·junit·单元测试·问题记录·自学笔记