对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单元测试。 - <font color="#000000"><font size="3">npm i -D jest
- {
- "scripts": {
- "test": "jest"
- }
- }</font></font>
复制代码 模拟VS Code node module Jest提供了一些mocking的选项,但是因为我们想要模拟整个vscode node module,所以最简单的办法是在与node_modules文件夹相同的位置(通常是项目的根目录)创建一个__mocks__文件夹,并在其中添加一个与要模拟的模块名称相同的文件(vscode.js)。 你不需要在测试代码中导入该模块,mock会自动加载它。Jest称此为manual mocks。 这种方法最大的好处是它能将我们的测试代码与所依赖的模块分离,使测试代码看起来更加整洁。这里有一个小问题,新加入的开发者需要知道__mocks__文件夹,否则很难理解单元测试是如何正常工作的,因为单元测试中并没有VS Code模块被模拟的代码。 以下就是对VS Code模块进行模拟的代码。我们并没有模拟整个API,你可以根据需要进行调整。 - <font color="#000000"><font size="3">// 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;</font></font>
复制代码 使用模拟的VS Code模块的示例 我的开源项目Git Mob for VS code中使用了这种方法,我将用其中的代码来说明如何使用模拟的VS Code模块。 下面的例子中,VS Code编辑器的状态栏会根据Git钩子prepare-commit-msg是否被调用来做相应的调整,你可以看到这里我并没有将vscode模块导入到我的测试文件中并对其进行模拟。 - <font color="#000000"><font size="3">// 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"
- })
- );
- });
- });</font></font>
复制代码你可以在这里查看源代码: - git-mob-hook-status.spec.js
- git-mob-hook-status.js
我能检查vscode模块中的方法是否被调用了吗? 你可以导入模拟的vscode模块。下面的代码中,我想要检查当用户修改co-author文件时onDidSaveTextDocument事件是否被订阅了。 - <font color="#000000"><font size="3">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)
- );
- // ...
- });
- // ...</font></font>
复制代码可以看到这里都是Jest mock API的标准用法,这意味着我们可以在代码中正常使用vscode模块的方法,而不受manual mock的任何限制。例如,我们还可以使用mockImplementation来修改实现。 更多示例可以查看这里的源代码: 编写单元测试最大的好处是可以快速得到反馈结果,如果你对TDD(Test-Driven Development,测试驱动开发)情有独钟,那么单元测试将使你对VS Code extension的开发更加信心满满。
|