Jest测试Next.js+Typesense应用时如何Mock搜索请求返回自定义数据
Jest/RTL 环境下Mock Typesense搜索请求方案
你可以根据测试场景选以下两种实现,都能完全拦截发往Typesense的请求、自定义返回内容,不会触发真实后端调用。
方案1:模块级Mock Typesense适配器(推荐,灵活度最高)
你的代码里是通过TypesenseInstantSearchAdapter生成searchClient传给InstantSearch,直接Mock整个适配器模块是侵入性最低、控制粒度最细的方案,不需要处理网络层逻辑。
- 在测试文件开头,或者Jest全局setup文件中添加模块Mock:
// Mock typesense-instantsearch-adapter模块 jest.mock('typesense-instantsearch-adapter', () => { const originalModule = jest.requireActual('typesense-instantsearch-adapter'); return { ...originalModule, // 重写适配器构造函数 default: jest.fn().mockImplementation(() => ({ searchClient: { // 所有搜索请求都会走这个mock的search方法 search: jest.fn((requests) => { // 可根据传入的requests参数(索引名、查询词、过滤条件等)返回不同数据 return Promise.resolve({ results: requests.map(req => ({ hits: [], // 这里替换成你需要的测试返回数据 nbHits: 0, page: 0, nbPages: 1, hitsPerPage: req.params.hitsPerPage || 20, processingTimeMS: 1, query: req.params.query || '', index: req.indexName })) }) }) } })) } })
- 单测中可动态修改返回值,覆盖不同场景:
test('搜索无结果时展示空状态', async () => { // 取出mock的search方法 const Adapter = require('typesense-instantsearch-adapter').default; const mockSearch = Adapter.mock.results[0].value.searchClient.search; // 临时修改本次调用的返回为空结果 mockSearch.mockResolvedValueOnce({ results: [{ hits: [], nbHits: 0, nbPages: 0, page: 0, hitsPerPage: 20 }] }) render(<Home />); expect(await screen.findByText('暂无搜索结果')).toBeInTheDocument(); })
- 可直接断言请求参数是否符合预期:
expect(mockSearch).toHaveBeenCalledWith( expect.arrayContaining([ expect.objectContaining({ indexName: SEARCH_INDEX.ITEMS, params: expect.objectContaining({ hitsPerPage: 20 }) }) ]) )
方案2:网络层拦截请求(适合需要监听出站请求的场景)
如果需要校验真实的请求格式、请求头,可以用网络拦截工具直接拦截发往Typesense服务的HTTP请求,最常用的是MSW(Mock Service Worker),和React Testing Library适配无侵入。
- 配置MSW拦截规则:
import { rest } from 'msw'; import { setupServer } from 'msw/node'; const testServer = setupServer( // 拦截所有域名下符合Typesense搜索规则的POST请求 rest.post('*/collections/*/documents/search', (req, res, ctx) => { // 可从req中读取请求参数、请求头、索引名做分支判断 return res( ctx.status(200), ctx.json({ hits: [], // 替换为自定义测试数据 found: 0, out_of: 0, page: 1, search_time_ms: 1 }) ) }) ) // 测试生命周期管理 beforeAll(() => testServer.listen()); afterEach(() => testServer.resetHandlers()); afterAll(() => testServer.close());
- 单测中可临时修改拦截返回,也可断言请求属性:
test('搜索请求携带正确的API Key', async () => { let capturedReq; testServer.use( rest.post('*/collections/*/documents/search', (req, res, ctx) => { capturedReq = req; return res(ctx.status(200), ctx.json({ hits: [], found: 0 })) }) ) render(<Home />); await screen.findByText('搜索组件加载完成'); expect(capturedReq.headers.get('X-TYPESENSE-API-KEY')).toBe('test-user-search-key'); })
注意事项
- Mock返回的结构需要和Typesense真实返回/InstantSearch要求的字段对齐,至少包含
hits、nbHits/found、nbPages、page、hitsPerPage字段,否则组件会渲染报错。 - 如果项目里用的是axios/fetch发起自定义Typesense请求,也可以直接用
jest.mock('axios')或者全局fetch mock拦截,逻辑和MSW一致。
内容的提问来源于stack exchange,提问作者Henrik
相关产品推荐
相关产品推荐

