對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單元測試。
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,你可以根據需要進行調整。
// 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模塊導入到我的測試文件中並對其進行模擬。
// 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" }) ); }); });
// 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事件是否被訂閱了。
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的開發更加信心滿滿。