簡體   English   中英

在函數內模擬函數並開玩笑地獲取調用計數

[英]mocking a function inside a function and getting calls count in jest

考慮script.js這個模塊:

const randomizeRange = (min, max) => {
  return Math.floor(Math.random() * (max - min) + min);
};

const randomArr = (n, min, max) => {
  const arr = [];
  for (let i = 0; i < n; i++) {
    arr.push(randomizeRange(min, max));
  }
  return arr;
};


module.exports = { randomArr, randomizeRange };

在下面的測試文件中, randomizeRangeMock的調用計數不應該是 100,因為它是在調用一次的randomArrMock內部調用的嗎? 為什么我得到0

const randomArr = require("./script").randomArr;
const randomizeRange = require("./script").randomizeRange;

const randomArrMock = jest.fn(randomArr);
const randomizeRangeMock = jest.fn(randomizeRange);

test("tests for randomArr", () => {
  expect(randomArrMock(100, 20, 1000)).toHaveLength(100);
  expect(randomArrMock.mock.calls.length).toBe(1)
  expect(randomizeRangeMock.mock.calls.length).toBe(100) //This fails because its 0
});

我很感激你試圖理解這個問題的jest ,所以我會盡我所能回答。 我通常建議您不要模擬函數,除非您對服務器或數據庫或其他任何東西進行某種復雜的后端調用。 您可以使用真實代碼(而不是模擬)進行的更真實的測試會更加有益。 這將是我對你前進的建議。

但就這個問題而言 - 這就是正在發生的事情。

你沒有看到你的模擬在這里以正確的方式工作的原因是因為你基本上創建了兩個單獨的模擬:

  • 您已經為randomizeArr創建了一個模擬。
  • 您已經為randomizeRange創建了另一個單獨的模擬。

當您在測試中調用randomArrMock(100, 20, 1000)時,這將按照您的指定為randomizeArr調用您的單個模擬,但該模擬絕對沒有任何存在的randomizeRange模擬的概念。 所以它永遠不會在幕后被調用。 所以這就是為什么你在最后看到呼叫數量為0原因。

只需在此處稍微更改您的設置以指定調用哪些模擬以及何時調用,以便按照您的期望看到兩者一起工作。

我在這里實際選擇的根本不是模擬您的randomizeRange方法。 在這種情況下,該方法調用了Math.floor ,這將更容易“監視”(這是我們將在此處使用的方法的提示)。 每次調用randomizeRange都會調用一次Math.floor方法,因此它將是一個理想的選擇。 它也應該被調用100次,與您的方法相同。

我們選擇spyOn方法而不是按照您已經完成的方式使用實際模擬的原因是因為我們希望保留原始行為,因此在不覆蓋預期行為的情況下監視調用該方法的次數是理想的。

因此,請保留您現有的randomizeArr模擬,如下所示:

const randomArrMock = jest.fn(randomArr);

現在為Math.floor設置間諜

const mathFloorSpy = jest.spyOn(Math.prototype, `floor`);

這個“間諜”將有效地監視此方法而不會覆蓋其行為。 如果您願意,您可以覆蓋該行為,但我們希望保持該行為完整無缺,同時只計算它被調用的次數。

所以現在當你運行你的測試套件時:

test("tests for randomArr", () => {
  expect(randomArrMock(100, 20, 1000)).toHaveLength(100);
  expect(randomArrMock.mock.calls.length).toBe(1)
  expect(mathFloorSpy.mock.calls.length).toBe(100)
});

現在這將通過,因為每次調用randomizeRange方法時都會調用Math.floor

最后不要忘記使用以下命令在測試完成后恢復原始Math.floor功能:

mathFloorSpy.mockRestore();

測試愉快!

這似乎是一個常見問題,我能找到的最“官方”解決方案是在 Jest GitHub 問題頁面上, 網址https://github.com/facebook/jest/issues/936#issuecomment-545080082

由於您使用的是require而不是使用 babel 轉譯的 ES 模塊,因此該鏈接問題中描述的解決方案似乎也不適用。

我會修改您的script.js模塊以直接引用使用的導出,以便模擬引用正確的函數:

exports.randomizeRange = (min, max) => {
    return Math.floor(Math.random() * (max - min) + min);
};

exports.randomArr = (n, min, max) => {
    const arr = [];
    for (let i = 0; i < n; i++) {
        arr.push(exports.randomizeRange(min, max));
    }
    return arr;
};

然后您的測試實現將引用模擬函數:

const myScript = require('../script');

jest.spyOn(myScript, 'randomArr');
jest.spyOn(myScript, 'randomizeRange');

test("tests for randomArr", () => {
    expect(myScript.randomArr(100, 20, 1000)).toHaveLength(100);
    expect(myScript.randomArr).toHaveBeenCalledTimes(1);
    expect(myScript.randomizeRange).toHaveBeenCalledTimes(100);
});

使用spyOntoHaveBeenCalledTimes可以提高這里的可讀性。


現在,給我自己的建議:編寫可測試的代碼。 如果您的代碼是可測試的,那么您可以輕松地為其編寫測試,並且可測試的代碼通常更加模塊化和靈活; 但不要專注於測試實現細節。

在您的示例中,如果您想公開一個randomArr方法,則不要同時公開randomizeRange 您的randomArr方法可以調用randomizeRange ,但這是您不應該擔心的實現細節。

如果您想讓您的randomArr更易於測試,請考慮將范圍方法(或范圍)設為參數。 然后您可以測試它,而無需測試它如何生成隨機范圍的一些實現。 像這樣的東西:

exports.randomArr = (n, min, max, randomRangeGenerator) => {
    const arr = [];
    for (let i = 0; i < n; i++) {
        arr.push(randomRangeGenerator(min, max));
    }
    return arr;
};

您也可以將此模式擴展到randomizeRange 為了測試該方法,要么將floorrandom函數作為參數傳遞,要么在測試中模擬它們(通過 Math 對象)。 在測試中使用Math.random很困難,因為它會在每次運行時產生不同的值,所以重要的是模擬。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM