用 mock 把 ES 單元測試@elastic/elasticsearch-mock 上手

一、為什么“單元測 ES”這么別扭?

測試 ES 代碼時,最直覺的做法是連真集群做集成測試(Docker 起個 ES),但:

  • 啟動 & 數據裝填慢,不利于并行
  • 網絡/磁盤抖動影響穩定性
  • 很多用例其實只想驗證我寫的邏輯,不是驗證 ES 自己。

單元測試更適合快速回歸。思路是:把客戶端的 HTTP 層換成 mock,其余組件照常運行。這就是官方 mock 庫 @elastic/elasticsearch-mock 的用武之地。

二、官方 JS 客戶端的內部構件(理解這一張圖,mock 不會走偏)

  • API layer:所有可調用的 ES API。
  • Transport:請求的準備、重試、sniff 等策略。
  • ConnectionPool:管理多個節點。
  • Serializer:JSON/Ndjson 序列化。
  • Connection:真正發 HTTP 的地方。

最佳 mock 點:Connection
我們只替換 Connection,其它(API、Transport、池、序列化)保持真實行為,既快又貼近真實調用路徑。

三、安裝與最小示例

npm i -D @elastic/elasticsearch-mock
npm i @elastic/elasticsearch
// test/info.mock.test.js  —— 最小可運行示例(Node >= 16)
const { Client } = require('@elastic/elasticsearch')
const Mock = require('@elastic/elasticsearch-mock')const mock = new Mock()
const client = new Client({cloud: { id: '<cloud-id>' },auth: { apiKey: 'base64EncodedKey' },// 關鍵:用 mock 替換 ConnectionConnection: mock.getConnection()
})// 定義一個最簡單的路由:GET /
mock.add({ method: 'GET', path: '/' }, () => ({ status: 'ok' }));(async () => {const res = await client.info()console.log(res) // => { status: 'ok' }
})()

要點

  • mock.getConnection() 給出一個“假 HTTP”連接對象;
  • 之后所有請求都不會真正出網,速度與穩定性拉滿

四、匹配策略:寬松 vs 嚴格

同一路徑,你可以按需要定義多條 mock,越具體的匹配優先生效

// 寬松:只看 method + path
mock.add({method: 'POST',path: '/indexName/_search'
}, () => ({hits: { total: { value: 1, relation: 'eq' }, hits: [{ _source: { baz: 'faz' } }] }
}))// 嚴格:連 body 也要完全匹配(深度相等)
mock.add({method: 'POST',path: '/indexName/_search',body: { query: { match: { foo: 'bar' } } }
}, () => ({hits: { total: { value: 0, relation: 'eq' }, hits: [] }
}))

規則:更具體(帶 body 的)覆蓋更寬松的。這樣你能同時覆蓋“默認搜索”和“特定查詢”的兩種分支。

五、動態路徑與通配

// 動態段:/:index/_count
mock.add({ method: 'GET', path: '/:index/_count' }, () => ({ count: 42 }))await client.count({ index: 'foo' }) // { count: 42 }
await client.count({ index: 'bar' }) // { count: 42 }
// 也支持通配符(如需要匹配一批相近路徑)

六、讓你的代碼“經得起風浪”

編寫“隨機失敗/間歇性 500”的用例,檢驗重試容錯是否健壯。

const { Client, errors } = require('@elastic/elasticsearch')mock.add({ method: 'GET', path: '/:index/_count' }, () => {if (Math.random() > 0.8) {// 方式 A(簡單):直接拋 JS Error(Transport 會當作失敗)const err = new Error('boom')err.statusCode = 500throw err// 方式 B(更貼近客戶端):拋客戶端的 ResponseError(不同版本構造略有差異)// throw new errors.ResponseError({ body: { error: 'fail' }, statusCode: 500 })}return { count: 42 }
})

提示:不同版本的 ResponseError 構造方式可能略有差異;如果不確定,拋普通 Error + 設置 statusCode 也能覆蓋你的重試/分支邏輯。

七、在 AVA 里寫測試(官方示例里的同款框架)

npm i -D ava

// test/search.ava.test.js
import test from 'ava'
import { Client } from '@elastic/elasticsearch'
import Mock from '@elastic/elasticsearch-mock'test('search: 默認與特定查詢兩條分支', async t => {const mock = new Mock()const client = new Client({ node: 'http://unit.test', Connection: mock.getConnection() })// 寬松分支mock.add({ method: 'POST', path: '/indexName/_search' }, () => ({hits: { total: { value: 1, relation: 'eq' }, hits: [{ _source: { baz: 'faz' } }] }}))// 嚴格分支(匹配 body)mock.add({method: 'POST',path: '/indexName/_search',body: { query: { match: { foo: 'bar' } } }}, () => ({ hits: { total: { value: 0, relation: 'eq' }, hits: [] } }))// 默認搜索const a = await client.search({ index: 'indexName', query: { match_all: {} } })t.is(a.hits.hits[0]._source.baz, 'faz')// 特定查詢const b = await client.search({ index: 'indexName', query: { match: { foo: 'bar' } } })t.is(b.hits.total.value, 0)
})

package.json 加腳本:

{"type": "module","scripts": { "test": "ava" }
}

八、在 Jest 里寫測試(更常用)

npm i -D jest @types/jest(TS 需要再裝 ts-jest)

// test/count.jest.test.js
const { Client } = require('@elastic/elasticsearch')
const Mock = require('@elastic/elasticsearch-mock')describe('count API', () => {test('動態路徑 & 固定返回', async () => {const mock = new Mock()const client = new Client({ node: 'http://unit.test', Connection: mock.getConnection() })mock.add({ method: 'GET', path: '/:index/_count' }, () => ({ count: 42 }))await expect(client.count({ index: 'alpha' })).resolves.toEqual({ count: 42 })await expect(client.count({ index: 'beta'  })).resolves.toEqual({ count: 42 })})
})

package.json

{"scripts": { "test": "jest" }
}

九、TypeScript 友好寫法

// test/info.ts
import { Client } from '@elastic/elasticsearch'
import Mock from '@elastic/elasticsearch-mock'const mock = new Mock()
const client = new Client({node: 'http://unit.test',Connection: mock.getConnection()
})mock.add({ method: 'GET', path: '/' }, () => ({ status: 'ok' }))export async function getInfo() {return client.info()
}

tsconfig.json:確保 "moduleResolution": "node", "esModuleInterop": true,Jest 用 ts-jest 即可。

十、進階手法

1) 校驗“我的代碼發出了期望的請求”

mock 的處理函數里可以檢查入參(如 body 中的 query/分頁條件),從而斷言業務層是否正確組織了請求。

mock.add({ method: 'POST', path: '/goods/_search' }, (params) => {// params.body 就是請求體if (params?.body?.size !== 10) throw new Error('page size must be 10')return { hits: { total: { value: 0, relation: 'eq' }, hits: [] } }
})

2) 順序響應(模擬滾動/重試)

同一路由注冊多次,按注冊順序命中,便于模擬“第一次失敗、第二次成功”的重試邏輯。

mock.add({ method: 'GET', path: '/:index/_count' }, () => { throw Object.assign(new Error('500'), { statusCode: 500 }) })
mock.add({ method: 'GET', path: '/:index/_count' }, () => ({ count: 42 }))

3) 與真實集成測試的分層配合

  • 單元測試:mock Connection,覆蓋邊界條件/重試/錯誤處理分支。
  • 集成測試:Docker 起一個真 ES(或 Testcontainers),驗證 mapping、腳本字段、聚合等“ES 自身語義”。

十一、最佳實踐與避坑清單

  • 每個測試用例新建一個 mock 實例:避免跨用例狀態污染。
  • 優先寫“寬松”匹配,再補“嚴格”匹配:覆蓋默認路徑后,針對關鍵分支加嚴格體檢。
  • 特意寫失敗用例:5xx、超時、斷線,確保重試/回退策略真的在跑。
  • 控制隨機性:用假隨機或 seed 固定,避免“隨機失敗”導致測試不穩定。
  • 特征:ES 版本差異:個別客戶端版本對錯誤對象/響應包裝略有差異;若你要斷言錯誤類型,建議使用客戶端自帶的 errors.*(或直接斷言 statusCode / name / message)。

十二、參考項目骨架(可抄)

your-project/
├─ src/
│  └─ search.js
├─ test/
│  ├─ info.mock.test.js
│  ├─ search.ava.test.js
│  └─ count.jest.test.js
├─ package.json
└─ tsconfig.json (若用 TS)

package.json(混合 AVA/Jest 也沒問題):

{"type": "module","scripts": {"test": "jest && ava"},"devDependencies": {"@elastic/elasticsearch-mock": "^x.y.z","ava": "^x.y.z","jest": "^x.y.z"},"dependencies": {"@elastic/elasticsearch": "^x.y.z"}
}

十三、結語

  • 把 Connection 換成 mock,你的測試就從“重集成”回到“輕單元”,速度與穩定性雙贏;
  • 寬松 + 嚴格匹配動態路徑/通配失敗注入,能覆蓋絕大多數線上分支;
  • 單測用 mock,回歸再配一小撮 Docker 集成測試做端到端兜底,是性價比最高的組合。

如果你愿意,我可以把上面 AVA/Jest/TS 的樣例整理成一個 examples/ 目錄(含 package.json、腳手架與說明),你直接 npm test 就能跑。需要我打包一下嗎?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/95504.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/95504.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/95504.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

《嵌入式Linux應用編程(三):Linux文件IO系統調用深度解析》

今日學習內容1. 文件IO與標準IO核心對比特性標準IO文件IO實現層C標準庫Linux內核系統調用緩沖機制全緩沖/行緩沖無緩沖&#xff08;實時讀寫&#xff09;操作對象FILE*流指針整型文件描述符&#xff08;fd&#xff09;移植性跨平臺兼容Linux特有典型應用場景普通文件操作硬件設…

數據結構之順序表相關算法題

目錄一、移除元素二、刪除有序數組中的重復項三、合并兩個有序數組總結一、移除元素 移除元素 - 力扣 思路一&#xff1a;就是創建一個臨時數組&#xff0c;對原數組進行遍歷&#xff0c;找出與val不同的數據放到新數組里&#xff0c;然后再將tmp中的數據導回原數組 這個思…

百勝軟件×華為云聯合賦能,“超級國民品牌”海瀾之家新零售加速前行

報道顯示&#xff0c;早在2012年海瀾之家就開始布局數字化征程&#xff0c;并于近年對公司全流程信息化進行綜合重構升級優化&#xff0c;在采銷協同、業財一體等方面突破原有架構&#xff0c;通過信息化架構的增強為業務發展提供支撐。作為新零售重要組成部分的海瀾電商信息化…

“Zen 5”: The AMD High-Performance 4nm x86-64 Microprocessor Core

Codenamed “Zen 5,” AMD’s next-generation, energy-efficient high-performance x86 core targets a wide array of client, server, and embedded markets. Fabricated in TSMC’s 4nm FinFET process, the 55mm2 core complex (CCX), shown in Fig. 2.1.1., contains 8.6…

Linux數據庫:【表的約束】【表的基本查詢】

目錄 一.表的約束 1.1空屬性 not null 1.2默認值 default ?空屬性和默認值一起使用&#xff1f; 1.3列描述 comment 1.4 zerofill 1.5 主鍵 1.6 自增長 1.7 唯一鍵 1.8 外鍵 二. 表的基本查詢 2.1 Create 2.1.1單行數據 全列插入 2.1.2多行數據 指定列插入 2…

AJAX RSS Reader

AJAX RSS Reader 引言 隨著互聯網的快速發展,信息量的爆炸式增長,用戶對信息獲取的便捷性和實時性提出了更高的要求。RSS(Really Simple Syndication)作為一種信息聚合技術,已經廣泛應用于新聞、博客、論壇等網絡平臺。AJAX(Asynchronous JavaScript and XML)技術則提…

從實驗室到落地:飛算JavaAI水位監測系統的工程化實踐

一、飛算JavaAI平臺簡介飛算JavaAI是國內領先的軟件開發智能平臺&#xff0c;通過AI技術賦能軟件開發全流程&#xff0c;幫助開發者實現"一人一項目&#xff0c;十人抵百人"的高效開發模式。平臺核心優勢包括&#xff1a; 智能代碼生成&#xff1a;基于自然語言描述自…

前端Vite介紹(現代化前端構建工具,由尤雨溪開發,旨在顯著提升開發體驗和構建效率)ES模塊(ESM)、與傳統Webpack對比、Rollup打包

文章目錄**1. 核心特性**- **極速啟動**&#xff1a;- **按需編譯與熱模塊替換&#xff08;HMR&#xff09;**&#xff1a;- **開箱即用**&#xff1a;- **生產環境優化**&#xff1a;- **插件系統**&#xff1a;**2. 工作原理****開發模式**- **基于 ESM 的按需加載**&#xf…

python sqlite3模塊

十分想念順店雜可。。。Python 的sqlite3模塊是標準庫中用于操作SQLite 數據庫的工具。SQLite 是一款輕量級嵌入式數據庫&#xff08;無需獨立服務器&#xff0c;數據存儲在單一文件中&#xff09;&#xff0c;適合小型應用、本地數據存儲或原型開發。sqlite3模塊提供了完整的 …

用 Python 繪制企業年度財務可視化報告 —— 從 Excel 到 9 種圖表全覆蓋

用 Python 繪制企業年度財務可視化報告 —— 從 Excel 到 9 種圖表全覆蓋在企業經營分析中&#xff0c;光看一堆財務數字很難直觀發現規律和問題。 如果能將這些數據轉化為可視化圖表&#xff0c;不僅更美觀&#xff0c;還能幫助管理層快速做出決策。今天&#xff0c;我就用 Py…

一次 Unity ? Android 基于 RSA?OAEP 的互通踩坑記

這篇分享&#xff0c;記錄我如何從“Base64 報錯/平臺不支持/解密失敗”一路定位到“填充算法不一致”的根因&#xff0c;并給出兩條穩定落地方案。同時整理了調試手冊、代碼片段和上線前自檢清單&#xff0c;方便你復用。 背景 Unity 端用公鑰加密一段緊湊 JSON&#xff08;i…

Go語言GC機制:高效并發回收解析

Go 語言的垃圾回收&#xff08;Garbage Collection&#xff0c;簡稱 GC&#xff09;是其自動內存管理的核心機制&#xff0c;旨在自動識別并回收不再被使用的內存&#xff0c;避免內存泄漏&#xff0c;減輕開發者的手動內存管理負擔。Go 的 GC 算法經歷了多次迭代優化&#xff…

imx6ull-驅動開發篇23——Linux 內核定時器實驗

目錄 實驗程序編寫 修改設備樹文件 定時器驅動程序 timer.c 測試 timerApp.c Makefile 文件 運行測試 實驗程序編寫 本講實驗&#xff0c;我們使用正點原子I.MX6U-ALPHA 開發板&#xff0c;通過linux內核定時器周期性的點亮和熄滅開發板上的 LED 燈&#xff0c; LED 燈…

IPTV系統:開啟視聽與管理的全新篇章

在當今數字化飛速發展的時代&#xff0c;IPTV系統正以前所未有的姿態&#xff0c;重塑著我們的視聽體驗與管理模式。它不僅僅是一套技術系統&#xff0c;更是連接信息、溝通情感、提升效率的橋梁&#xff0c;為各個領域帶來了全新的變革與發展機遇。從電視直播的角度來看&#…

PyTorch筆記9----------Cifar10圖像分類

1.圖像分類網絡模型框架解讀 分類網絡的基本結構 數據加載模塊&#xff1a;對訓練數據加載數據重組&#xff1a;組合成網絡需要的形式&#xff0c;例如預處理、增強、各種網絡處理、loss函數計算優化器 數據加載模塊 使用公開數據集&#xff1a;torchvision.datasets使用自定義…

飛凌OK3568開發板QT應用程序編譯流程

飛凌OK3568開發板QT應用程序編譯流程開發環境&#xff1a;ubuntu20.04&#xff08;主機&#xff09;、飛凌OK3568開發板一般在linux系統下開發用于ARM開發板的QT應用程序時&#xff0c;直接在主機上開發然后進行交叉編譯即可&#xff0c;但有時候ARM開發板的廠家提供的SDK中可能…

飛算JavaAI合并項目實戰:7天完成3年遺留系統重構

引言 企業數字化進程中&#xff0c;遺留系統改造始終是CIO面臨的頭號難題。某電商平臺的實踐數據顯示&#xff1a;3年以上的Java項目平均存在47%的冗余代碼&#xff0c;63%的架構設計不符合當前業務需求&#xff0c;進行系統性重構需要投入相當于原開發量200%的資源。傳統&quo…

衛星速度增量和比沖及推力之間的關系

一、定義1.1.比沖&#xff08;Isp&#xff09;&#xff1a;比沖是衡量發動機性能的重要指標&#xff0c;反映了單位重量推進劑在發動機中產生的沖量&#xff0c;單位為米/秒&#xff08;m/s&#xff09;&#xff0c;代表燃料燃燒時噴流速度。這個單位與速度單位“米/秒”相同&a…

MATLAB繪制各種心形曲線

1.方程(1)心形線的經典隱函數方程為&#xff1a;(2)參數方程&#xff08;更平滑的心形&#xff09;&#xff1a;(3)極坐標心形線(4)參數方程&#xff08;3D心形&#xff09;(5)隱函數3D心形2. MATLAB代碼clc;close all;clear all;warning off;%清除變量 rand(seed, 100); randn…

Django REST Framework視圖

Django REST Framework (DRF) 視圖類詳解DRF 提供了豐富的視圖類來構建 API&#xff0c;從基礎到高級&#xff0c;滿足不同復雜度的需求。以下是 DRF 的主要視圖類及其使用場景&#xff1a;1. 基礎視圖類APIView所有 DRF 視圖的基類&#xff0c;相當于 Django 的 View 類的增強…