一、为什么“单元测 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 类的增强…