首頁 >web前端 >js教程 >Jest 簡介:單元測試、模擬和非同步程式碼

Jest 簡介:單元測試、模擬和非同步程式碼

Linda Hamilton
Linda Hamilton原創
2024-11-01 00:23:28677瀏覽

Introduction to Jest: Unit Testing, Mocking, and Asynchronous Code

笑話簡介

Jest 是一個用來測試 JavaScript 程式碼的函式庫。

這是一個由 Facebook 維護的開源項目,它特別適合 React 程式碼測試,但不僅限於此:它可以測試任何 JavaScript 程式碼。它的優點是:

  • 速度很快
  • 它可以執行快照檢定
  • 它固執己見,提供開箱即用的一切,無需您做出選擇
export default function sum(a, n) {
  return a + b;
}

divide.test.js

import sum from './sum';

// Describe the test and wrap it in a function.
it('adds 1 + 2 to equal 3', () => {
  const result = sum(1, 2);

  // Jest uses matchers, like pretty much any other JavaScript testing framework.
  // They're designed to be easy to get at a glance;
  // here, you're expecting `result` to be 3.
  expect(result).toBe(3);
});

匹配器

匹配器是一種可以讓您測試值的方法。

  • toBe 比較嚴格相等,使用 ===
  • toEqual 比較兩個變數的值。如果它是一個物件或數組,它會檢查所有屬性或元素的相等性
  • 傳遞 null 值時 toBeNull 為 true
  • 傳遞定義值時 toBeDefined 為 true(與上方相反)
  • 傳遞未定義值時 toBeUndefine 為 true
  • toBeCloseTo 用於比較浮點數值,避免捨入錯誤
  • toBeTruthy true 如果該值被視為 true (就像 if 那樣)
  • toBeFalsy 如果該值被認為是假(就像 if 一樣),則為 true
  • 如果 Expect() 的結果高於參數
  • ,則 toBeGreaterThan true
  • toBeGreaterThanOrEqual 如果 Expect() 的結果等於參數或高於參數
  • ,則為 true
  • 如果 Expect() 的結果低於參數
  • ,則 toBeLessThan true
  • toBeLessThanOrEqual 如果 Expect() 的結果等於參數或低於參數
  • ,則為 true
  • toMatch 用於將字串與正規表示式模式配對進行比較
  • toContain 用於數組,如果預期數組在其元素集中包含參數
  • ,則為 true
  • toHaveLength(number):檢查陣列的長度
  • toHaveProperty(key, value):檢查物件是否具有屬性,並可選擇檢查其值
  • toThrow 檢查您傳遞的函數是否拋出異常(一般情況下)或特定異常
  • toBeInstanceOf():檢查物件是否為類別的實例

依賴關係

依賴項是您的應用程式所依賴的一段程式碼。它可以是我們專案中的函數/物件或第三方依賴項(例如 axios)

當您自己的應用程式沒有一段程式碼就無法運作時,它就變成真正的依賴項。

例如,如果您在應用程式中實作一項功能來傳送電子郵件或發出 api 請求或建置設定物件等

兩種方法我們可以在js專案的程式碼中加入依賴項:

進口

export default function sum(a, n) {
  return a + b;
}

依賴注入

只是一個簡單概念的奇特術語。

如果您的函數需要外部相依性的某些功能,只需將其作為參數注入即可。

import sum from './sum';

// Describe the test and wrap it in a function.
it('adds 1 + 2 to equal 3', () => {
  const result = sum(1, 2);

  // Jest uses matchers, like pretty much any other JavaScript testing framework.
  // They're designed to be easy to get at a glance;
  // here, you're expecting `result` to be 3.
  expect(result).toBe(3);
});

單元測試

單元測試由軟體開發人員編寫和運行,以確保應用程式的一部分(稱為「單元」)滿足其設計並按預期運行。

我們想要單獨測試我們的程式碼,我們不關心任何依賴項的實際實作。
我們想要驗證

  • 我們的程式碼單元如預期般運作
  • 回傳預期結果
  • 以其應有的方式呼叫任何協作者(依賴項)

這就是模擬我們的依賴關係發揮作用的地方。

嘲笑

在單元測試中,模擬為我們提供了存根依賴項所提供的功能的能力,以及意味著觀察我們的程式碼如何與依賴項互動。

當將依賴項直接包含到我們的測試中成本昂貴或不切實際時,例如,當您的程式碼對 API 進行 HTTP 呼叫或與資料庫層互動時,模擬特別有用。

最好刪除這些依賴項的回應,同時確保它們按要求被呼叫。這就是模擬派上用場的地方。

透過使用模擬函數,我們可以知道以下:

  • 收到的來電數量
  • 參數 每次呼叫時使用的值。
  • 每次呼叫時的「上下文」或這個值。
  • 函數如何退出以及產生了哪些值

開玩笑地嘲笑

建立模擬函數有多種方法。

  • jest.fn 方法讓我們可以直接建立一個新的模擬函數。
  • 如果您正在模擬物件方法,則可以使用 jest.spyOn。
  • 如果你想模擬整個模組,你可以使用 jest.mock。

jest.fn 方法本身就是一個高階函數。

這是一個工廠方法,用於建立新的、未使用的模擬函數。

JavaScript 中的函數是一等公民,它們可以作為參數傳遞。

每個模擬函數都有一些特殊的性質。模擬屬性是基礎。此屬性是一個對象,其中包含有關如何呼叫函數的所有模擬狀態資訊。該物件包含三個數組屬性:

  • 呼叫 [每次呼叫的參數]
  • 實例 [每次呼叫時的「this」值]
  • Results [函數退出的值],results 屬性有型別(return 或 throw)和值
    • 函數明確傳回一個值。
    • 函數運行完成,沒有 return 語句(相當於回傳 undefined
    • 函數拋出錯誤。
export default function sum(a, n) {
  return a + b;
}
  • https://codesandbox.io/s/implementing-mock-functions-tkc8b

模擬基礎

import sum from './sum';

// Describe the test and wrap it in a function.
it('adds 1 + 2 to equal 3', () => {
  const result = sum(1, 2);

  // Jest uses matchers, like pretty much any other JavaScript testing framework.
  // They're designed to be easy to get at a glance;
  // here, you're expecting `result` to be 3.
  expect(result).toBe(3);
});

模擬注入的依賴項

import { name, draw, reportArea, reportPerimeter } from './modules/square.js';

類比模組

使用 jest.fn 模擬函數

// Constructor Injection

// DatabaseManager class takes a database connector as a dependency
class DatabaseManager {
    constructor(databaseConnector) {
        // Dependency injection of the database connector
        this.databaseConnector = databaseConnector;
    }

    updateRow(rowId, data) {
        // Use the injected database connector to perform the update
        this.databaseConnector.update(rowId, data);
    }
}

// parameter injection, takes a database connector instance in as an argument; easy to test!
function updateRow(rowId, data, databaseConnector) {
    databaseConnector.update(rowId, data);
}

這種類型的嘲笑不太常見,原因如下:

  • jest.mock 會自動為模組中的所有函數執行此操作
  • jest.spyOn 做同樣的事情,但允許恢復原始功能

使用 jest.mock 類比模組

更常見的方法是使用 jest.mock 自動將模組的所有匯出設定為 Mock 函數

// 1. The mock function factory
function fn(impl = () => {}) {
  // 2. The mock function
  const mockFn = function(...args) {
    // 4. Store the arguments used
    mockFn.mock.calls.push(args);
    mockFn.mock.instances.push(this);
    try {
      const value = impl.apply(this, args); // call impl, passing the right this
      mockFn.mock.results.push({ type: 'return', value });
      return value; // return the value
    } catch (value) {
      mockFn.mock.results.push({ type: 'throw', value });
      throw value; // re-throw the error
    }
  }
  // 3. Mock state
  mockFn.mock = { calls: [], instances: [], results: [] };
  return mockFn;
}

使用 jest.spyOn 監視或模擬函數

有時您只想觀看一個方法被調用,但保留原始實作。其他時候,您可能想要模擬實現,但稍後在套件中恢復原始版本。

test("returns undefined by default", () => {
  const mock = jest.fn();

  let result = mock("foo");

  expect(result).toBeUndefined();
  expect(mock).toHaveBeenCalled();
  expect(mock).toHaveBeenCalledTimes(1);
  expect(mock).toHaveBeenCalledWith("foo");
});

恢復原來的實現

const doAdd = (a, b, callback) => {
  callback(a + b);
};

test("calls callback with arguments added", () => {
  const mockCallback = jest.fn();
  doAdd(1, 2, mockCallback);
  expect(mockCallback).toHaveBeenCalledWith(3);
});

JavaScript 和事件循環

JavaScript 是單執行緒的: 一次只能執行一個任務。通常這沒什麼大不了的,但現在想像一下你正在運行一個需要30 秒的任務.. 是的.. 在該任務期間,我們等待30 秒,然後才會發生其他事情(JavaScript 默認在瀏覽器的主線程上運行,所以整個UI都卡住了)。
現在是 2020 年,沒有人想要一個緩慢、反應遲鈍的網站。

幸運的是,瀏覽器為我們提供了一些 JavaScript 引擎本身不提供的功能:Web API。這包括 DOM APIsetTimeoutHTTP 請求 等。這可以幫助我們創建一些非同步非阻塞行為

export default function sum(a, n) {
  return a + b;
}
  • 呼叫堆疊 - 當我們呼叫一個函數時,它會被加入到稱為呼叫堆疊的東西。
  • WebAPI - setTimeout 由 WebAPI 提供,採用回調函數並設定計時器,而不阻塞主執行緒
  • 佇列 - 當計時器結束時,回呼被加入到佇列
  • 事件循環 - 檢查呼叫堆疊是否為空,檢查佇列中是否有要執行的回調,然後移至要執行的呼叫堆疊
import sum from './sum';

// Describe the test and wrap it in a function.
it('adds 1 + 2 to equal 3', () => {
  const result = sum(1, 2);

  // Jest uses matchers, like pretty much any other JavaScript testing framework.
  // They're designed to be easy to get at a glance;
  // here, you're expecting `result` to be 3.
  expect(result).toBe(3);
});

使用 Jest 測試非同步程式碼

Jest 通常希望同步執行測試的函數

如果我們執行非同步操作,但我們不讓 Jest 知道它應該等待測試結束,則會給出誤報。

import { name, draw, reportArea, reportPerimeter } from './modules/square.js';

非同步模式
JavaScript 中有多種處理非同步操作的模式;最常用的是:

  • 回調
  • Promise 和非同步/等待

測試回調

你不能在回調中進行測試,因為 Jest 不會執行它 - 測試檔案的執行在回呼被呼叫之前結束。要解決此問題,請將參數傳遞給測試函數,您可以方便地呼叫“done”。 Jest 會等到您呼叫 done() 後再結束測試:

// Constructor Injection

// DatabaseManager class takes a database connector as a dependency
class DatabaseManager {
    constructor(databaseConnector) {
        // Dependency injection of the database connector
        this.databaseConnector = databaseConnector;
    }

    updateRow(rowId, data) {
        // Use the injected database connector to perform the update
        this.databaseConnector.update(rowId, data);
    }
}

// parameter injection, takes a database connector instance in as an argument; easy to test!
function updateRow(rowId, data, databaseConnector) {
    databaseConnector.update(rowId, data);
}

承諾

對於傳回 Promise 的函數,我們從測試中傳回一個 Promise:

// 1. The mock function factory
function fn(impl = () => {}) {
  // 2. The mock function
  const mockFn = function(...args) {
    // 4. Store the arguments used
    mockFn.mock.calls.push(args);
    mockFn.mock.instances.push(this);
    try {
      const value = impl.apply(this, args); // call impl, passing the right this
      mockFn.mock.results.push({ type: 'return', value });
      return value; // return the value
    } catch (value) {
      mockFn.mock.results.push({ type: 'throw', value });
      throw value; // re-throw the error
    }
  }
  // 3. Mock state
  mockFn.mock = { calls: [], instances: [], results: [] };
  return mockFn;
}

異步/等待

為了測試傳回 Promise 的函數,我們也可以使用 async/await,這使得語法非常簡單明了:

test("returns undefined by default", () => {
  const mock = jest.fn();

  let result = mock("foo");

  expect(result).toBeUndefined();
  expect(mock).toHaveBeenCalled();
  expect(mock).toHaveBeenCalledTimes(1);
  expect(mock).toHaveBeenCalledWith("foo");
});

尖端

  • 我們需要很好地理解我們的函數的作用以及我們要測試的內容
  • 思考我們正在測試的程式碼的行為
  • 設定舞台:
    • 模擬/監視任何依賴項
    • 我們的程式碼是否與全域物件互動?我們也可以嘲笑/監視他們
    • 我們的測驗是否與 DOM 互動?我們可以建構一些假元素來使用
    • 建構你的測驗
    • 鑑於...
    • 我打電話時......
    • 然後...我期待......
const doAdd = (a, b, callback) => {
  callback(a + b);
};

test("calls callback with arguments added", () => {
  const mockCallback = jest.fn();
  doAdd(1, 2, mockCallback);
  expect(mockCallback).toHaveBeenCalledWith(3);
});

連結

  • https://medium.com/@rickhanlonii/understanding-jest-mocks-f0046c68e53c
  • https://jestjs.io/docs/en/mock-functions
  • https://codesandbox.io/s/implementing-mock-functions-tkc8b
  • https://github.com/BulbEnergy/jest-mock-examples
  • https://dev.to/lydiahallie/javascript-visualized-event-loop-3dif
  • https://jestjs.io/docs/en/asynchronous
  • https://www.pluralsight.com/guides/test-asynchronous-code-jest

以上是Jest 簡介:單元測試、模擬和非同步程式碼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn