JavaScript >> Javascript 文檔 >  >> JavaScript

在 Sinon.js 中使用 Mocks 在 JavaScript 中進行測試

簡介

測試“模擬”是在模擬其功能時替換真實對象的對象。模擬還對如何使用被測試的函數有期望。

在某些單元測試用例中,我們可能希望結合間諜的功能,觀察被調用的方法的行為,以及存根的功能,以替換方法的功能,以確保我們不進行實際的函數調用但仍然能夠相應地監控我們的目標函數的行為。在這種情況下,我們可以使用模擬。

在本文中,我們將試圖了解什麼是模擬以及如何在單元測試中使用它們。然後,我們將獲得使用 Sinon.js 模擬 HTTP 請求的實踐經驗。

本文是我們使用 Sinon.js 進行單元測試技術系列的第三篇。我們建議您也閱讀我們之前關於此主題的文章:

  • 在帶有 Sinon.js 的 JavaScript 中使用存根進行測試
  • 通過 Sinon.js 在 JavaScript 中使用間諜進行測試
  • 在帶有 Sinon.js 的 JavaScript 中使用 Mocks 進行測試(你在這裡 )

什麼是 Mocks?

模擬結合了兩個間諜的功能 和存根 ,這意味著它們替換了目標函數,但同時為我們提供了觀察函數是如何被調用的能力。

此外,模擬具有稱為期望的內置斷言。您可以預先定義如何使用模擬函數的期望。如果你的 mock 沒有滿足它的期望,你的測試就會失敗。

例如,讓我們考慮一個與數據庫通信以保存聯繫人詳細信息的函數。使用模擬,而不是真正的數據庫,我們的函數將命中一個假數據庫對象。我們可以確定它會給出什麼樣的響應。我們還將說明應調用數據庫的次數以及應使用的參數。

最後,作為測試的一部分,我們驗證我們的數據庫模擬被調用的確切次數是我們預期的。我們還檢查它是否僅使用我們的函數應該提供的參數來調用。

了解了 mock 是什麼之後,現在讓我們看看可以使用它們的情況。

為什麼要使用 Mocks?

在驗證如何在函數中使用外部依賴項時,Mocks 很有用。對以下內容感興趣時使用模擬:

  • 確認完全使用了您的外部依賴項
  • 驗證您的外部依賴項是否正確使用
  • 確保您的函數可以處理來自外部依賴項的不同響應。

想像一下,您正在測試一個與第三方 API 對話以獲取一些用戶數據的函數。要向外部 API 發出請求,您需要先進行幾次調用以進行身份驗證。在測試中使用真正的 API 已經變得不方便了。此外,在運行測試時,您可能並不總是有互聯網連接來訪問 API。

使用模擬,我們將返回虛假響應。我們現在可以快速測試我們的函數在給定特定格式的假數據時是否正確運行。我們還將知道我們的函數使用正確的參數向 API 發出請求。

現在讓我們看看如何使用 Sinon.js 創建模擬。

使用 Sinon.js 創建 Mock

我們將使用 Sinon.js 模擬來自 JSON API 的響應,該響應檢索相冊中的照片列表。除了 Sinon.js,我們將使用 Mocha 和 Chai 來設置和運行測試。在繼續之前,您可以閱讀我們的指南以了解更多信息。

設置

創建一個名為 SinonMock 的目錄 並搬進去:

$ mkdir SinonMock
$ cd SinonMock

然後我們將使用 NPM 初始化一個項目來跟踪我們創建的項目文件:

$ npm init -y

接下來,我們將安裝 Mocha 和 Chai 作為測試依賴項來運行我們的測試,以及 Sinon.js:

$ npm i mocha chai sinon --save-dev

完成設置後,讓我們模擬一個 HTTP 請求。

使用 Sinon.js 模擬 HTTP 調用

在我們之前關於測試間諜的文章中,我們監視了對相冊 API 的 HTTP 請求。我們將在本文中繼續使用該示例。

SinonMock 的根目錄下創建一個文件 目錄並將其命名為 index.js

$ touch index.js

在創建的文件中,輸入以下代碼:

const request = require('request');

module.exports = {
    getAlbumById: async function(id) {
        const requestUrl = `https://jsonplaceholder.typicode.com/albums/${id}/photos?_limit=3`;
        return new Promise((resolve, reject) => {
            request.get(requestUrl, (err, res, body) => {
                if (err) {
                    return reject(err);
                }
                resolve(JSON.parse(body));
            });
        });
    }
};

免費電子書:Git Essentials

查看我們的 Git 學習實踐指南,其中包含最佳實踐、行業認可的標準以及隨附的備忘單。停止谷歌搜索 Git 命令並真正學習 它!

回顧一下,getAlbumById() 是一個調用返回照片列表的 JSON API 的函數。我們提供專輯 ID 作為函數參數。我們之前已經探索過 request.get() 上的存根和監視 方法。

現在,我們將模擬 request 對象並檢查 get() 方法根據需要調用一次,並驗證它是否接收到正確的參數。然後,我們將根據從我們的模擬返回的內容驗證我們的函數是否具有正確的屬性。

SinonMock 的根目錄下創建另一個文件 目錄並將其命名為 index.test.js

$ touch index.test.js

打開index.test.js 使用編輯器創建文件並輸入以下代碼:

const expect = require('chai').expect;
const request = require('request');
const sinon = require('sinon');
const index = require('./index');

describe('with mock: getPhotosByAlbumId', () => {
    it('should getPhotosByAlbumId', (done) => {
        let requestMock = sinon.mock(request);
        const myPhotos = [{
            "albumId": 1,
            "id": 1,
            "title": "accusamus beatae ad facilis cum similique qui sunt",
            "url": "https://via.placeholder.com/600/92c952",
            "thumbnailUrl": "https://via.placeholder.com/150/92c952"
        },
        {
            "albumId": 1,
            "id": 2,
            "title": "reprehenderit est deserunt velit ipsam",
            "url": "https://via.placeholder.com/600/771796",
            "thumbnailUrl": "https://via.placeholder.com/150/771796"
        },
        {
            "albumId": 1,
            "id": 3,
            "title": "officia porro iure quia iusto qui ipsa ut modi",
            "url": "https://via.placeholder.com/600/24f355",
            "thumbnailUrl": "https://via.placeholder.com/150/24f355"
        }];

        requestMock.expects("get")
            .once()
            .withArgs('https://jsonplaceholder.typicode.com/albums/2/photos?_limit=3')
            .yields(null, null, JSON.stringify(myPhotos));

        index.getAlbumById(2).then((photos) => {
            expect(photos.length).to.equal(3);
            photos.forEach((photo) => {
                expect(photo).to.have.property('id');
                expect(photo).to.have.property('title');
                expect(photo).to.have.property('url');
            });

            requestMock.verify();
            requestMock.restore();
            done();
        });
    });
});

在上面的測試用例中,我們首先創建一個 request 的模擬 使用 sinon.mock() 的對象 並將其命名為 requestMock . requestMock 對象具有request的功能 對象,但函數默認什麼都不做。

在提供了一些固定的照片數據後,我們覆蓋了原來的 get() 使用 Sinon.js 的 mock API 的 expect() 請求對象的方法 方法。

expect() method 接受一個參數,這是我們預期將使用的模擬對象的方法。

once() 方法斷言我們的期望被調用一次。在這種情況下,get() 請求對象的方法只會被調用一次。

withArgs() 方法斷言我們期望 get() 使用我們提供給它的參數數組調用方法。在我們的例子中是 API 的 URL。

yields() 方法將數據放入我們的模擬對象接受的回調中。在這種情況下,我們的錯誤和響應都是 null 但我們的身體有一個 JSON 響應。

設置完成後,我們調用 getAlbumById() 方法並檢查返回的照片是否具有正確的屬性。

注意 verify() requestMock 的調用 反對確認我們的期望得到滿足。如果期望失敗,測試將拋出異常。然後我們調用 restore() 方法來丟棄我們測試創建的mock並恢復原始請求對象。

當你運行這個測試時,你應該得到以下結果:

$ mocha index.test.js

with mock: getPhotosByAlbumId
    ✓ should getPhotosByAlbumId


  1 passing (13ms)

✨  Done in 0.72s.

為了確認我們的模擬行為,讓我們看看如果我們更改與之通信的 URL,預期是否會失敗。在你的 index.js 文件,更改:

const requestUrl = `https://jsonplaceholder.typicode.com/albums/${id}/photos?_limit=3`;

致:

const requestUrl = `https://example.com`;

現在再次運行測試:

$ mocha index.test.js

由於我們將輸入更改為 get() 方法,URL 參數不再匹配我們測試中的內容。當我們運行測試時,我們會得到這個輸出:

> mocha index.test.js



  with mock: getPhotosByAlbumId
(node:85434) UnhandledPromiseRejectionWarning: ExpectationError: Unexpected call: get(https://example.com, function () {})
    Expected get(https://jsonplaceholder.typicode.com/albums/2/photos?_limit=3[, ...]) once (never called)

偉大的!我們非常確定,我們的 mock 將保證我們的函數按預期運行!

通過使用模擬,我們已經能夠獲得間諜和存根的好處。我們能夠檢查我們的函數是否只被調用了一次,並且使用了正確的參數,這是間諜為我們提供的好處。我們還能夠對請求進行存根,這樣我們就不會對 API 進行實際的 HTTP 調用,從而確保我們的測試能夠快速運行。

結論

單元測試中的模擬通過替換像存根這樣的函數來結合間諜和存根的功能,同時為我們提供觀察函數以檢查它們如何被調用的方法,間諜提供給我們的功能。然後,Mocks 為我們提供了驗證我們的函數在測試中是如何使用的好處。

在本文中,我們介紹了單元測試中模擬的概念,並了解瞭如何模擬 HTTP 調用。想了解更多關於Sinon.js mocks的內容,可以查看mocks API的官方文檔。


Tutorial JavaScript 教程
  1. 讓我們使用 Node.js 和 Heroku 創建一個 Twitter Bot (3/3)

  2. React Internals (Part 1) - 基本概念和先決條件

  3. 介紹 Hamo - 零吊鉤🎣

  4. 如何使用 Hubot 為您的 Slack 頻道增添趣味

  5. 使用 React、WebRTC 進行視頻通話和屏幕共享

  6. 部署 Angular 應用程序以激增

  7. 使用 Vuex 自動保存

  1. 使用 DOM 第 1 部分

  2. 如何使用 JavaScript 重新加載 ReCaptcha?

  3. 賽普拉斯命令(獲取、單擊、查找)

  4. 如何使用 React 和 Solidity 構建經典的 Web3 NFT 鑄幣 Dapp:第 1 部分

  5. 我開始學習HTML,它太容易了。正確的?

  6. 面試

  7. JavaScript 切換多種情況 |示例代碼

  1. 使用 CodeLobster 加速 jQuery 開發

  2. JavaScript 頁面滾動進度條

  3. Shopify 從零開始的應用程序 #1 - 簡介

  4. GeoJSON 文件:拒絕執行腳本,因為它的 MIME 類型 (”) 不可執行,並且啟用了嚴格的 MIME 類型檢查