iT邦幫忙

2023 iThome 鐵人賽

0
自我挑戰組

用ChatGPT詠唱來完成工作與點亮前後端技能樹系列 第 31

【Day31】ChatGPT請教教我:Jest 單元測試(下) - 完整語法&教學

  • 分享至 

  • xImage
  •  

目前這個系列已挑選ChatGPT、前端&React相關章節出版成書!
感謝大家的閱讀與支持!
《用 ChatGPT 詠唱來點亮 React & 前端技能樹》
歷經重新整理、校正、編排、與修改後,讓章節內容之間的銜接更平順,除了補充上更多的解說之外,甚至有部分章節為重新撰寫的內容,讓理解變得更輕鬆!
目前已在天瓏書局博客來金石堂等通路都買得到了! 也有電子書的選擇!
歡迎各位點擊連結選購,謝謝大家~
https://ithelp.ithome.com.tw/upload/images/20240419/20135750lQM12by6v1.jpg


上一篇將討論了Jest的基本介紹和安裝

Jest基本上就是針對「小單元」進行題目與答案的設計
在輸入指定的input的情況下,必定獲得固定的output,且能夠預期到執行結果

這個章節要來討論Jest的語法以及實際使用、設計

語法總覽

https://ithelp.ithome.com.tw/upload/images/20231012/20135750AZRaoIJa1a.png

當然,以下是 Jest 單元測試的基本語法和一些常用的功能:

1. 測試函數

使用 testit 函數來定義一個測試案例。

test('測試條件描述', () => {
  // 測試邏輯
});

2. 斷言(Assertion)

使用 expect 函數來進行斷言,確保函數的輸出符合預期。

test('1 + 1 應該等於 2', () => {
  expect(1 + 1).toBe(2);
});

3. 匹配器(Matchers)

Jest 提供多種匹配器來檢查值。以下是一些常用的匹配器:

  • toBe(value): 檢查是否等於特定值。
  • toEqual(value): 檢查是否等於特定值(對象或數組)。
  • not.toBe(value): 檢查是否不等於特定值。
  • toBeNull(): 檢查是否為 null
  • toBeDefined(): 檢查是否已定義。
  • toBeTruthy(): 檢查是否為真值。
  • toBeFalsy(): 檢查是否為假值。
  • toContain(value): 檢查數組或字串是否包含特定元素或子串。
  • toHaveLength(value): 檢查數組或字串的長度。
  • 等等。

4. 異步測試

如果你的函數是異步的,可以使用 async/await.then() 來處理。

test('測試異步函數', async () => {
  const result = await asyncFunction();
  expect(result).toBe(expectedValue);
});

5. 測試運行前後的生命周期函數

有時你可能需要在每個測試案例運行前後做一些設置或清理工作。可以使用 beforeEachafterEach 函數:

beforeEach(() => {
  // 在每個測試案例運行前執行
});

afterEach(() => {
  // 在每個測試案例運行後執行
});

6. 測試套件

你也可以使用 describe 函數來組織你的測試案例:

describe('測試套件描述', () => {
  test('測試案例 1', () => {
    // 測試邏輯
  });

  test('測試案例 2', () => {
    // 測試邏輯
  });
});

這樣可以更好地組織你的測試,並提供更清晰的描述。

7. 模擬函數

使用 jest.fn() 可以模擬一個函數,以測試函數的調用情況。

const mockFunction = jest.fn();

mockFunction();

expect(mockFunction).toHaveBeenCalled();

8. 模擬時間

你可以使用 jest.useFakeTimers() 來模擬時間:

jest.useFakeTimers();

setTimeout(() => {
  // 執行某些操作
}, 1000);

jest.runAllTimers(); // 立即執行所有計時器

這是一個簡單的 Jest 單元測試基礎語法和一些常用功能的介紹。透過這些功能,你可以開始撰寫有效的單元測試以確保你的程式碼的品質和可靠性。如果你有任何進一步的問題,請隨時提問!

OK,常用的類型與語法ChatGPT幫我們很粗略的舉出了
我們接下來要針對各點去詳細的探討與了解

1. 定義測試案例:test & it

https://ithelp.ithome.com.tw/upload/images/20231012/20135750JEuGMxKw5f.png

當你定義測試案例時,你實際上是在告訴 Jest 如何測試你的程式碼。以下是針對「定義測試案例」的詳細教學和說明:

1. 使用 testit 函數

你可以使用 testit 函數來定義一個測試案例。這些函數具有相同的功能,你可以根據個人喜好選擇其中之一。以下是如何使用 test 函數定義一個測試案例:

test('描述測試的條件', () => {
  // 測試邏輯
});
  • '描述測試的條件' 是一個字串,用於描述這個測試案例的目的。它通常應該清晰地說明你正在測試什麼。
  • 函數 () => { /* 測試邏輯 */ } 包含了實際的測試邏輯。在這個函數中,你將驗證你的程式碼是否符合預期。

2. 斷言(Assertion)

測試案例中的核心部分是斷言,它們是用來檢查程式碼的期望行為是否符合實際行為。你可以使用 expect 函數來進行斷言。以下是一個基本的示例:

test('1 + 1 應該等於 2', () => {
  expect(1 + 1).toBe(2);
});

在這個範例中,expect(1 + 1) 是一個斷言,它用來檢查 1 + 1 的結果是否等於 2。如果不等於,測試就會失敗。

Jest 提供許多內建的匹配器(matchers),你可以使用它們來進行更複雜的斷言。例如,使用 toEqual 來比較物件或數組,使用 toBeTruthytoBeFalsy 來檢查真值或假值,等等。

3. 測試案例的結果

當你執行 Jest 測試時,它會運行你定義的測試案例並報告結果。結果可能有以下情況:

  • 測試通過:如果所有斷言都符合預期,測試案例將通過。
  • 測試失敗:如果任何一個斷言不符合預期,測試案例將失敗,並報告相關信息。
  • 測試未運行:如果你未執行該測試案例,它將被忽略。

4. 測試案例描述的重要性

測試案例的描述非常重要,它應該清楚地說明你的目標和預期行為。這對於理解測試結果和問題排查非常有幫助。

5. 範例

以下是一個完整的範例,定義了一個測試案例,測試一個簡單的函數:

// 要測試的函數
function add(a, b) {
  return a + b;
}

// 測試案例
test('add 函數正確相加', () => {
  const result = add(2, 3);
  // 期望結果等於 5
  expect(result).toBe(5);
});

這個測試案例測試了 add 函數是否正確地相加兩個數字,並使用 toBe 匹配器來斷言結果是否等於 5。

希望這個詳細的說明能夠幫助你理解如何定義 Jest 測試案例以驗證你的程式碼。如果有進一步的問題,請隨時提問!

所以test 和 it 在jest中是一樣的,根據個人喜好選擇就行
ChatGPT也解釋了他是建立「一個測試案例」的語法

2 & 3. 斷言(Assertion)&匹配器(Matchers)

https://ithelp.ithome.com.tw/upload/images/20231012/20135750D2FlwosaC7.png

當進行單元測試時,斷言和匹配器(Matchers)是非常重要的工具,它們用於驗證程式碼的行為是否符合預期。以下是斷言和匹配器的詳細介紹和完整教學:

斷言(Assertion):

在單元測試中,斷言用於檢查測試中的實際輸出是否符合預期結果。在 Jest 中,你可以使用 expect 函數來進行斷言。

使用方法:

test('描述測試的條件', () => {
  // 測試邏輯
  expect(實際輸出).匹配器(預期輸出);
});

例如:

test('1 + 1 應該等於 2', () => {
  expect(1 + 1).toBe(2);
});

在這個例子中,expect(1 + 1) 是一個斷言,它會檢查 1 + 1 的結果是否等於 2

常用匹配器(Matchers):

Jest 提供了多種匹配器來檢查值。以下是一些常用的匹配器:

1. toBe(value)

檢查是否等於特定值。

test('1 + 1 應該等於 2', () => {
  expect(1 + 1).toBe(2);
});

2. toEqual(value)

檢查是否等於特定值(對象或數組)。

test('對象相等', () => {
  const obj1 = { a: 1, b: 2 };
  const obj2 = { a: 1, b: 2 };
  expect(obj1).toEqual(obj2);
});

3. not.toBe(value)

檢查是否不等於特定值。

test('1 + 1 不應該等於 3', () => {
  expect(1 + 1).not.toBe(3);
});

4. toBeNull()

檢查是否為 null

test('變量應該為 null', () => {
  const myNullValue = null;
  expect(myNullValue).toBeNull();
});

5. toBeDefined()

檢查是否已定義。

test('變量應該被定義', () => {
  const myValue = 10;
  expect(myValue).toBeDefined();
});

6. toBeTruthy()toBeFalsy()

檢查是否為真值或假值。

test('變量應該為真值', () => {
  const myValue = true;
  expect(myValue).toBeTruthy();
});

test('變量應該為假值', () => {
  const myValue = false;
  expect(myValue).toBeFalsy();
});

7. toContain(value)

檢查數組或字串是否包含特定元素或子串。

test('字串包含特定子串', () => {
  const str = 'Hello, World!';
  expect(str).toContain('World');
});

test('數組包含特定元素', () => {
  const arr = [1, 2, 3, 4];
  expect(arr).toContain(3);
});

8. toHaveLength(value)

檢查數組或字串的長度。

test('字串長度為 10', () => {
  const str = 'Hello, Jest!';
  expect(str).toHaveLength(10);
});

test('數組長度為 4', () => {
  const arr = [1, 2, 3, 4];
  expect(arr).toHaveLength(4);
});

這只是一些常用的匹配器,Jest 還提供了許多其他匹配器以滿足不同的測試需求。

希望這些詳細的介紹和示例可以幫助你理解斷言和匹配器的使用方法。如果你有任何進一步的問題,請隨時提問!

https://ithelp.ithome.com.tw/upload/images/20231012/20135750t2EPryvkMA.png

當然!除了上述提到的常用匹配器之外,Jest 還提供了許多其他常見的匹配器,以下是其中一些:

1. toBeGreaterThan(value)toBeLessThan(value)

檢查數字是否大於或小於特定值。

test('10 應該大於 5', () => {
  expect(10).toBeGreaterThan(5);
});

test('3 應該小於 5', () => {
  expect(3).toBeLessThan(5);
});

2. toBeGreaterThanOrEqual(value)toBeLessThanOrEqual(value)

檢查數字是否大於等於或小於等於特定值。

test('10 應該大於等於 5', () => {
  expect(10).toBeGreaterThanOrEqual(5);
});

test('3 應該小於等於 5', () => {
  expect(3).toBeLessThanOrEqual(5);
});

3. toBeCloseTo(value, numDigits)

用於比較浮點數,確保它們在特定小數位數下相等。

test('0.1 + 0.2 應該接近 0.3', () => {
  expect(0.1 + 0.2).toBeCloseTo(0.3, 5); // 5 表示小數點後五位
});

4. toMatch(pattern)

用於檢查字串是否與正則表達式模式匹配。

test('字串匹配正則表達式', () => {
  expect('hello').toMatch(/h/);
});

5. toThrow(error?)

用於測試函數是否拋出了一個特定的錯誤。

function throwError() {
  throw new Error('This is an error');
}

test('函數應該拋出錯誤', () => {
  expect(throwError).toThrow();
  expect(throwError).toThrow(Error);
  expect(throwError).toThrow('This is an error');
});

6. toContainEqual(item)

用於檢查數組是否包含特定元素,可以用於深度比較。

test('數組包含特定元素', () => {
  const arr = [{ id: 1 }, { id: 2 }, { id: 3 }];
  expect(arr).toContainEqual({ id: 2 });
});

7. toBeInstanceOf(Class)

用於檢查值是否屬於特定類別的實例。

class MyClass {}
const instance = new MyClass();

test('變量應該是 MyClass 的實例', () => {
  expect(instance).toBeInstanceOf(MyClass);
});

8. toHaveProperty(keyPath?, value?)

用於檢查對象是否具有特定屬性,或者該屬性的值是否符合預期。

const myObject = {
  prop1: 'value1',
  prop2: {
    subprop: 'value2',
  },
};

test('myObject 應該具有特定屬性', () => {
  expect(myObject).toHaveProperty('prop1');
  expect(myObject).toHaveProperty('prop2.subprop', 'value2');
});

這只是一部分常用的匹配器,Jest 還提供了許多其他匹配器,可以根據需要查閱 Jest 官方文檔以獲得更詳細的資訊。希望這些匹配器能夠滿足你的測試需求!如果你有任何進一步的問題,請隨時提問!

ChatGPT上述的舉例蠻充分的,針對各種結果可以用各種方式去判斷是否符合預期

不管是等於、包含、大於小於、浮點數處理、長度、是否定義、是否符合格式、是否拋出錯誤
連物件的比較、類別的實例、確認是否有某些屬性等等判斷,也都有支援並提供範例

如果實際使用上有其他疑問,也可以直接問ChatGPT,這邊就不當作字典來詢問了

4. 異步測試

https://ithelp.ithome.com.tw/upload/images/20231012/20135750CqQWe9G8NE.png

異步測試是用於測試非同步程式碼的一種方式。在 JavaScript 中,異步程式碼通常涉及到使用回呼函數、Promise、async/await 等特性。在 Jest 中,你可以使用特定的方法來處理異步測試。

以下是異步測試的詳細介紹和完整教學:

使用 done

在 Jest 中,你可以使用一個叫做 done 的參數,來告訴 Jest 這是一個異步測試,當異步操作完成時,你需要呼叫 done 函數來通知 Jest 測試已完成。

例如,假設你有一個異步函數 fetchData

function fetchData(callback) {
  setTimeout(() => {
    callback('hello world');
  }, 1000);
}

你可以使用 done 參數來測試這個異步函數:

test('fetchData 函數應該返回 "hello world"', (done) => {
  function callback(data) {
    expect(data).toBe('hello world');
    done(); // 告訴 Jest 測試已完成
  }

  fetchData(callback);
});

使用 Promise:

如果你的異步函數返回一個 Promise,你可以使用 return 來告訴 Jest 等待 Promise 完成。

例如,假設你有一個返回 Promise 的異步函數 fetchData

function fetchData() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('hello world');
    }, 1000);
  });
}

你可以使用 return 來等待 Promise 完成:

test('fetchData 函數應該返回 "hello world"', () => {
  return fetchData().then(data => {
    expect(data).toBe('hello world');
  });
});

使用 async/await:

如果你的異步函數返回一個 Promise,你也可以使用 async/await 來編寫更簡潔的測試。

例如:

async function fetchData() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('hello world');
    }, 1000);
  });
}

test('fetchData 函數應該返回 "hello world"', async () => {
  const data = await fetchData();
  expect(data).toBe('hello world');
});

透過 async/await,我們可以在測試中像同步程式碼一樣進行撰寫,讓程式碼更易讀且易於理解。

超時設置:

Jest 預設情況下,會等待測試函數執行完畢,但你也可以使用 setTimeout 來模擬長時間運行的異步操作。

test('測試異步函數', () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('hello world');
    }, 5000);
  }).then(data => {
    expect(data).toBe('hello world');
  });
}, 10000); // 設定超時時間為 10 秒

在這個例子中,測試函數會等待最多 10 秒,如果超過這個時間,測試會被標記為失敗。

希望這個詳細的介紹和完整的教學能幫助你理解如何在 Jest 中進行異步測試。如果你有任何進一步的問題,請隨時提問!

5. 測試生命周期函數

https://ithelp.ithome.com.tw/upload/images/20231012/20135750taMIl5rGbS.png

在 Jest 中,你可以使用生命周期函數來在每個測試案例運行前或運行後執行特定的代碼。這允許你進行一些初始化或清理的工作,以確保測試的環境是符合預期的。

以下是 Jest 中常用的生命周期函數:

1. beforeAllafterAll

beforeAll 會在所有測試案例運行之前執行一次,而 afterAll 會在所有測試案例運行完成後執行一次。

beforeAll(() => {
  // 在所有測試案例運行之前執行一次的代碼
});

afterAll(() => {
  // 在所有測試案例運行完成後執行一次的代碼
});

2. beforeEachafterEach

beforeEach 會在每個測試案例運行前執行,而 afterEach 會在每個測試案例運行後執行。

beforeEach(() => {
  // 在每個測試案例運行前執行的代碼
});

afterEach(() => {
  // 在每個測試案例運行後執行的代碼
});

完整示例:

假設你有一個簡單的計算函數:

// math.js
function add(a, b) {
  return a + b;
}

module.exports = add;

你想要在測試運行前後輸出一些信息:

// math.test.js
const add = require('./math');

beforeAll(() => {
  console.log('所有測試案例開始執行前');
});

afterAll(() => {
  console.log('所有測試案例完成後');
});

beforeEach(() => {
  console.log('每個測試案例開始前');
});

afterEach(() => {
  console.log('每個測試案例結束後');
});

test('1 + 1 應該等於 2', () => {
  expect(add(1, 1)).toBe(2);
});

test('2 + 2 應該等於 4', () => {
  expect(add(2, 2)).toBe(4);
});

當你運行這些測試時,控制台會輸出如下:

所有測試案例開始執行前
每個測試案例開始前
1 + 1 應該等於 2
每個測試案例結束後
每個測試案例開始前
2 + 2 應該等於 4
每個測試案例結束後
所有測試案例完成後

這樣你可以在測試運行前後執行一些初始化或清理的工作,以確保測試環境的正確性。

希望這個詳細的介紹和完整的教學能幫助你理解 Jest 中的生命周期函數。如果你有任何進一步的問題,請隨時提問!

https://ithelp.ithome.com.tw/upload/images/20231012/20135750b8qV6xihgK.png

這四個生命周期函數(beforeAllafterAllbeforeEachafterEach)在 Jest 中通常用於以下情況:

1. beforeAll

  • 初始化共享資源:當你的測試套件中的多個測試案例需要共享一些資源(例如初始化一個數據庫連接),你可以在 beforeAll 中執行這些初始化操作。
  • 設置環境:如果你需要在所有測試案例運行前設置特定的環境,可以使用 beforeAll 來執行相關的設置。

2. afterAll

  • 清理資源:在所有測試案例完成後,你可以使用 afterAll 來進行清理工作,例如關閉數據庫連接或釋放其他資源。
  • 還原環境:如果你在 beforeAll 中進行了一些環境設置,可能需要在所有測試案例完成後還原環境。

3. beforeEach

  • 初始化每個測試案例:當你需要在每個測試案例運行前執行一些初始化操作時,可以使用 beforeEach
  • 重置狀態:如果你的測試案例影響了某些狀態,你可以在 beforeEach 中重置這些狀態,以保證每個測試案例的獨立性。

4. afterEach

  • 清理每個測試案例:當你需要在每個測試案例運行後進行一些清理操作時,可以使用 afterEach
  • 驗證結果:你可以在 afterEach 中檢查每個測試案例的結果或進行其他相關的操作。

總的來說,這些生命周期函數提供了一個在測試運行前後執行代碼的機會,以確保測試環境的正確性和獨立性。

請注意,這些生命周期函數可以在測試套件中的任何位置進行定義,它們的執行順序是按照它們被定義的順序來執行的。

6. 分類測試案例:describe

https://ithelp.ithome.com.tw/upload/images/20231012/20135750Br4np1dQ3Y.png

describe 函數是 Jest 中一個用於組織和描述測試套件的功能。它允許你將多個測試案例分組到一個共同的描述內,以提高測試代碼的可讀性和組織性。以下是 describe 的詳細介紹和完整教學:

使用 describe

你可以使用 describe 函數來創建測試套件,並將相關的測試案例放在該測試套件中。 describe 接受兩個參數:描述文字和一個函數,該函數包含了該測試套件中的測試案例。

describe('描述文字', () => {
  // 在這裡定義測試案例
});

完整示例:

假設你有一個 math.js 檔案,其中包含一個簡單的加法函數:

// math.js
function add(a, b) {
  return a + b;
}

module.exports = add;

你可以使用 describe 來組織測試案例:

const add = require('./math');

describe('加法函數', () => {
  test('1 + 1 應該等於 2', () => {
    expect(add(1, 1)).toBe(2);
  });

  test('2 + 2 應該等於 4', () => {
    expect(add(2, 2)).toBe(4);
  });
});

在這個示例中,我們使用 describe 創建了一個測試套件,名稱為 "加法函數",然後在該測試套件中包含了兩個測試案例,分別測試 1 + 1 和 2 + 2 的結果。

好處:

  • 組織性describe 允許你按功能或模組組織你的測試案例,使測試代碼更易於理解和維護。
  • 可讀性:描述文字可以提供對測試套件內容的說明,使其他開發者能夠快速理解你的測試用例。
  • 狀態隔離describe 可以幫助你在測試之間創建狀態隔離,以確保測試獨立運行。
  • 運行控制:你可以選擇運行特定的測試套件,從而更快地運行相關的測試。

巢狀 describe

你可以嵌套 describe 函數以更深入地組織你的測試套件,這特別適用於複雜的測試情境。

describe('外層測試套件', () => {
  describe('內層測試套件 1', () => {
    test('測試案例 1', () => {
      // 測試邏輯
    });

    test('測試案例 2', () => {
      // 測試邏輯
    });
  });

  describe('內層測試套件 2', () => {
    test('測試案例 3', () => {
      // 測試邏輯
    });

    test('測試案例 4', () => {
      // 測試邏輯
    });
  });
});

這樣的結構可以更清晰地表示測試用例之間的關係。

希望這個詳細的介紹和完整的教學有助於你理解如何使用 describe 函數來組織和描述測試套件。如果你有任何進一步的問題,請隨時提問!

https://ithelp.ithome.com.tw/upload/images/20231012/20135750Nd3BK0hCM3.png

除了用於組織和描述測試套件,describe 還可以用於以下一些進階的測試情境:

1. 異步測試

可以在 describe 內部使用 beforeAllafterAllbeforeEachafterEach 等函數來初始化或清理異步操作的資源。

describe('異步測試', () => {
  let data;

  beforeAll(async () => {
    data = await fetchData();
  });

  test('資料應為 "hello world"', () => {
    expect(data).toBe('hello world');
  });
});

2. 只運行特定的測試

可以在 describe 區塊內使用 test.onlyit.only 來指定只運行該區塊的測試。

describe('僅運行這個測試', () => {
  test.only('這個測試將被執行', () => {
    // 測試邏輯
  });

  test('這個測試不會被執行', () => {
    // 測試邏輯
  });
});

3. 略過特定的測試

可以在 describe 區塊內使用 test.skipit.skip 來指定跳過該區塊的測試。

describe('略過這個測試', () => {
  test.skip('這個測試將被略過', () => {
    // 測試邏輯
  });

  test('這個測試會被執行', () => {
    // 測試邏輯
  });
});

4. 共用變數和設定

可以在 describe 區塊內部設定變數或配置,讓所有的測試案例都可以使用。

describe('共用變數和設定', () => {
  let sharedVariable;

  beforeAll(() => {
    sharedVariable = '共用的變數';
  });

  test('第一個測試案例', () => {
    expect(sharedVariable).toBe('共用的變數');
  });

  test('第二個測試案例', () => {
    expect(sharedVariable).toBe('共用的變數');
  });
});

這樣,sharedVariable 變數可以在所有測試案例中共享使用。

5. 動態生成測試案例

可以在 describe 內部動態生成測試案例,這在某些情況下非常有用。

describe.each([
  [1, 1, 2],
  [2, 2, 4],
  [3, 3, 6],
])('加法函數', (a, b, expected) => {
  test(`${a} + ${b} 應該等於 ${expected}`, () => {
    expect(add(a, b)).toBe(expected);
  });
});

在這個示例中,describe.each 會根據提供的數組動態生成測試案例,這樣可以測試多組不同的輸入。

總的來說,describe 非常靈活,可以應用於許多不同的測試情境,幫助你組織、執行和控制測試套件。

7. 模擬函數

https://ithelp.ithome.com.tw/upload/images/20231012/20135750GiHAqphRCL.png

模擬(mocking)在測試中是一個常見的技巧,用於替代實際的物件、函數或服務,以便進行測試。在 Jest 中,你可以使用 Jest 的模擬功能來模擬函數、模組、類別等,以簡化測試的設置和確保測試環境的控制。以下是模擬的詳細介紹和完整教學:

模擬函數:

1. 模擬函數的基本用法:

要模擬一個函數,你可以使用 jest.fn() 創建一個模擬函數,然後可以設置它的行為,例如返回特定的值或拋出特定的錯誤。

const myFunction = jest.fn();

// 設置模擬函數的行為
myFunction.mockReturnValue(42);

// 呼叫模擬函數
console.log(myFunction()); // 輸出: 42

2. 模擬函數的參數和返回值:

你可以使用模擬函數的 mockImplementation 方法設置函數的實現,可以根據參數返回不同的值。

const add = jest.fn((a, b) => a + b);

console.log(add(2, 3)); // 輸出: 5
console.log(add(4, 5)); // 輸出: 9

3. 驗證模擬函數的調用:

你可以使用 expect 斷言來驗證模擬函數的調用次數和傳入的參數。

const myFunction = jest.fn();

myFunction(1, 2);

// 驗證模擬函數被呼叫一次
expect(myFunction).toBeCalledTimes(1);

// 驗證模擬函數被傳入 1 和 2 作為參數
expect(myFunction).toBeCalledWith(1, 2);

模擬模組:

1. 模擬模組的基本用法:

你可以使用 jest.mock() 函數模擬模組的行為,這樣當測試中引入該模組時,將使用模擬版本。

// math.js
export function add(a, b) {
  return a + b;
}

// math.test.js
jest.mock('./math'); // 模擬 math 模組

import { add } from './math';

// 現在 add 函數已經是一個模擬函數
add.mockReturnValue(42);

console.log(add(2, 3)); // 輸出: 42

2. 模擬模組的實際內容:

你可以使用 jest.requireActual() 來引入模組的實際內容,然後根據需要模擬特定部分。

// math.js
export function add(a, b) {
  return a + b;
}

export function subtract(a, b) {
  return a - b;
}

// math.test.js
import * as math from './math';

// 引入模組的實際內容
jest.requireActual('./math');

// 模擬 subtract 函數
math.subtract = jest.fn();
math.subtract.mockReturnValue(10);

console.log(math.add(2, 3)); // 實際的 add 函數
console.log(math.subtract(5, 3)); // 模擬的 subtract 函數,輸出: 10

模擬類別:

你可以使用 jest.mock() 來模擬類別的行為,

這樣在測試中創建類別的實例時,將使用模擬版本。

// MyClass.js
export default class MyClass {
  getValue() {
    return 42;
  }
}

// myClass.test.js
jest.mock('./MyClass'); // 模擬 MyClass 類別

import MyClass from './MyClass';

const instance = new MyClass();

// 設置模擬函數的行為
instance.getValue.mockReturnValue(100);

console.log(instance.getValue()); // 輸出: 100

這些是 Jest 中模擬的基本用法和一些進階應用情境。模擬讓你可以在測試中控制特定的函數、模組或類別的行為,使測試更容易進行和維護。

https://ithelp.ithome.com.tw/upload/images/20231013/20135750G7UGV5Vj50.png

模擬函數在測試中的使用情境如下:

  1. 依賴外部資源:當函數依賴於外部資源(例如網絡請求、數據庫查詢等)時,你可以使用模擬函數來模擬這些外部資源的返回值,以保證測試的獨立性。

    // 假設有一個 fetchUserData 函數,它進行了一個網絡請求以獲取用戶數據
    function fetchUserData() {
      return fetch('<https://api.example.com/user>').then(res => res.json());
    }
    
    // 在測試中,使用模擬函數來模擬網絡請求的返回值
    jest.mock('node-fetch');
    
    import fetch from 'node-fetch';
    
    test('fetchUserData 測試', async () => {
      fetch.mockResolvedValue({ id: 1, name: 'John Doe' });
    
      const userData = await fetchUserData();
    
      expect(userData).toEqual({ id: 1, name: 'John Doe' });
    });
    
  2. 函數有副作用:當函數執行時會對外部狀態進行更改(例如修改全局變數、寫入文件等),你可以使用模擬函數來檢查它的副作用。

    // 假設有一個函數 addToCounter,它會將傳入的數字加到全局計數器上
    let counter = 0;
    
    function addToCounter(num) {
      counter += num;
    }
    
    // 在測試中,使用模擬函數來模擬函數的執行
    test('addToCounter 測試', () => {
      addToCounter = jest.fn();
    
      addToCounter(5);
    
      expect(addToCounter).toHaveBeenCalledWith(5);
    });
    
  3. 函數非常複雜:當函數的實現非常複雜,而你只關心它的返回值,你可以使用模擬函數來簡化測試。

    // 假設有一個很複雜的函數,我們只關心它是否返回了期望的結果
    function complexFunction() {
      // 一長串的複雜邏輯...
    }
    
    // 在測試中,使用模擬函數來簡化測試
    test('complexFunction 測試', () => {
      complexFunction = jest.fn().mockReturnValue('expected result');
    
      const result = complexFunction();
    
      expect(result).toBe('expected result');
    });
    
  4. 測試錯誤情境:當你希望測試函數在特定情境下拋出錯誤,你可以使用模擬函數來模擬這種情境。

    // 假設有一個函數,當傳入特定參數時會拋出一個錯誤
    function throwErrorIfInputIsNegative(num) {
      if (num < 0) {
        throw new Error('Input must be non-negative');
      }
      return num;
    }
    
    // 在測試中,使用模擬函數來模擬拋出錯誤的情境
    test('throwErrorIfInputIsNegative 測試', () => {
      throwErrorIfInputIsNegative = jest.fn().mockImplementation(num => {
        if (num < 0) {
          throw new Error('Input must be non-negative');
        }
        return num;
      });
    
      expect(() => throwErrorIfInputIsNegative(-1)).toThrow('Input must be non-negative');
    });
    

這些是使用模擬函數的一些常見情境和相應的範例。模擬函數允許你在測試中對特定行為進行控制,確保你可以有效地進行測試並驗證你的程式碼的行為。

8. 模擬時間

https://ithelp.ithome.com.tw/upload/images/20231013/20135750PZxZy0lgjF.png

在測試中模擬時間是一個常見的需求,特別是當你的應用程序中涉及到時間相關的操作時(例如定時器、時間戳等)。Jest 提供了一個稱為 jest.useFakeTimers() 的功能,可以讓你輕鬆地模擬時間。以下是模擬時間的詳細介紹和完整教學:

使用 jest.useFakeTimers()

首先,在你的測試文件中,你需要在測試開始前調用 jest.useFakeTimers(),這將啟用 Jest 的時間模擬功能。

beforeEach(() => {
  jest.useFakeTimers();
});

afterEach(() => {
  jest.useRealTimers(); // 恢復真實的時間
});

模擬定時器:

1. setTimeout

你可以使用 jest.advanceTimersByTime(ms) 來模擬定時器,它會將時間向前推進指定的毫秒數。

test('測試 setTimeout', () => {
  const callback = jest.fn();

  setTimeout(callback, 1000);

  jest.advanceTimersByTime(1000); // 將時間向前推進 1000 毫秒

  expect(callback).toBeCalled();
});

2. setInterval

對於 setInterval,你可以使用 jest.advanceTimersByTime(ms) 來模擬多次調用。

test('測試 setInterval', () => {
  const callback = jest.fn();

  setInterval(callback, 1000);

  jest.advanceTimersByTime(1000); // 將時間向前推進 1000 毫秒,模擬第一次調用
  jest.advanceTimersByTime(1000); // 將時間再向前推進 1000 毫秒,模擬第二次調用

  expect(callback).toHaveBeenCalledTimes(2);
});

模擬當前時間:

你可以使用 Date.now() 來模擬當前的時間。

test('模擬當前時間', () => {
  const now = Date.now();

  // 模擬當前時間為 1609459200000 (2023-01-01T00:00:00.000Z)
  jest.spyOn(Date, 'now').mockImplementation(() => now);

  expect(Date.now()).toBe(now);
});

測試異步操作:

當你的代碼中包含異步操作時(例如 Promise、setTimeout、setInterval 等),你可以使用模擬時間來進行測試。

test('測試異步操作', () => {
  const callback = jest.fn();

  setTimeout(callback, 1000);

  jest.advanceTimersByTime(1000); // 將時間向前推進 1000 毫秒

  expect(callback).toBeCalled();
});

這樣,你可以在測試中控制時間的前進,以確保異步操作被正確地執行。

總的來說,使用 Jest 的時間模擬功能可以幫助你測試與時間相關的代碼,確保其行為正確。這在處理定時器、異步操作等情境中非常有用。

統整重點

https://ithelp.ithome.com.tw/upload/images/20231013/20135750RXMnRq6OwH.png

綜合以上內容,以下是有關 Jest 測試的主要重點:

  1. 單元測試的基本概念和原則
    • 單元測試是一種軟體測試方法,用於驗證程式中的個別組件(函數、模組、類別等)的行為。
    • 單元測試的目標是確保每個組件按照預期運作,並提前發現和修復錯誤。
  2. Jest 簡介
    • Jest 是一個流行的 JavaScript 測試框架,特別適用於 React、Node.js 和前端開發。
    • Jest 具有內建斷言庫和豐富的功能,使測試更容易進行。
  3. Jest 單元測試的基本教學
    • 使用 describe 函數來組織測試套件,以提高測試代碼的可讀性和組織性。
    • 使用 test 函數來定義測試案例,並使用斷言來驗證結果。
    • 使用 beforeEachafterEach 函數來執行測試之前和之後的操作。
  4. 斷言(Assertion)和匹配器(Matchers)
    • 使用斷言來檢查測試結果是否符合預期。
    • Jest 提供多種匹配器,例如 expect(x).toBe(y)expect(array).toContain(value) 等,用於不同的比較情境。
  5. 模擬函數、模組和類別
    • 使用 jest.fn() 來模擬函數的行為,並設定其返回值和調用驗證。
    • 使用 jest.mock() 來模擬模組的行為,特別是當模組依賴外部資源時。
    • 使用 jest.spyOn() 來模擬類別方法的行為。
  6. 模擬時間
    • 使用 jest.useFakeTimers() 來模擬時間,特別是在測試中涉及定時器或異步操作時。
    • 使用 jest.advanceTimersByTime() 來控制時間的前進,以確保異步操作被執行。

結語:Jest 是一個強大且靈活的測試框架,它提供了豐富的工具和功能,使單元測試變得更容易。透過遵循上述的基本原則,你可以建立健壯、可維護且可靠的測試套件,幫助你開發更高質量的軟體。無論你是初學者還是有經驗的前端工程師,Jest 都是一個有用的工具,可幫助你在專案中實行單元測試。

結語

本篇與上一篇對話記錄在這邊:
https://chat.openai.com/share/65f2c43f-325f-46c1-910a-fbe938468343

ChatGPT幫我們總覽的語法和內容,我覺得算是蠻齊全的
接下來幾篇都是要討論各種自動測試

下一篇!E2E測試!Cypress!
我是踏入自動測試的前端工程師一宵三筵,我們明天見!


上一篇
【Day30】ChatGPT請教教我:Jest 單元測試(上) - 基本介紹&起始安裝
下一篇
【Day32】ChatGPT請教教我:E2E測試!Cypress!(上)- 安裝&設定
系列文
用ChatGPT詠唱來完成工作與點亮前後端技能樹56
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言