在單元測試中使用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單元測試。

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的開發更加信心滿滿。

原文地址:Unit test & mock VS Code extension API with Jest

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章