React Bootstrap Typeahead测试未触发API调用问题排查
React Bootstrap AsyncTypeahead测试:axios.get spy未触发的问题解决
问题场景
为React Bootstrap AsyncTypeahead编写测试时,期望输入值触发HTTP GET请求,但jest spy始终未被调用,相关代码及错误如下:
组件代码
import React, { useState } from 'react'; import { render } from 'react-dom'; import './style.css'; import { AsyncTypeahead } from 'react-bootstrap-typeahead'; import 'react-bootstrap-typeahead/css/Typeahead.css'; import axios from 'axios'; export const Description = () => { const [tyOptions, setOpts] = useState([]); const handleSearch = (id) => { axios .get(`https://jsonplaceholder.typicode.com/todos/${id}`) .then(({ options }) => { // 此处解构错误,接口返回数据在data字段而非options console.log(options); setOpts(options); }); }; return ( <div> <AsyncTypeahead id="test" labelKey="title" minLength={1} onSearch={handleSearch} options={tyOptions} placeholder="Search todo by id." renderMenuItemChildren={(option) => ( <> <span>Test</span> </> )} /> </div> ); };
测试代码
describe("App Component Testing", () => { beforeEach(() => { jest.clearAllMocks(); }); it('should get typeahead data', async () => { let axios = jest.spyOn(axios, 'get').mockReturnValue(Promise.resolve( { data: [1, 2, 3, 4, 5] } )); let component; await act(async () => { component = render(<Description/>); }); let inputEl = component.queryByPlaceholderText(/Search todo by id./); userEvent.click(inputEl); userEvent.type(inputEl, 'test val'); expect(axios.get).toHaveBeenCalled(); }); });
错误信息
expect(jest.fn()).toHaveBeenCalled(); Expected number of calls: => 1 Received number of calls: 0
问题原因及解决步骤
1. 修复axios spy的变量覆盖问题
测试代码中重新声明let axios,覆盖了原本导入的axios实例,导致spy没有作用到组件实际使用的axios上。
修改方式:
去掉变量声明,直接对导入的axios进行spy:
import axios from 'axios'; // 确保导入axios // ... it('should get typeahead data', async () => { const axiosGetSpy = jest.spyOn(axios, 'get').mockResolvedValue({ data: [1, 2, 3, 4, 5] }); // ... expect(axiosGetSpy).toHaveBeenCalled(); });
2. 处理异步操作的等待逻辑
userEvent.type是异步操作,且AsyncTypeahead的onSearch触发后axios请求也是异步的,需要用waitFor等待spy被调用,否则断言会在请求触发前执行。
修改后的测试代码片段:
it('should get typeahead data', async () => { const axiosGetSpy = jest.spyOn(axios, 'get').mockResolvedValue({ data: [{ id: 1, title: 'Test Todo' }] }); const { queryByPlaceholderText } = render(<Description/>); const inputEl = queryByPlaceholderText(/Search todo by id./); userEvent.click(inputEl); await userEvent.type(inputEl, '1'); // 输入单个字符即可触发minLength=1的搜索 await waitFor(() => { expect(axiosGetSpy).toHaveBeenCalledWith('https://jsonplaceholder.typicode.com/todos/1'); }); });
3. 修复组件中的响应数据解构错误
组件中then(({ options }) => { ... })是错误的,jsonplaceholder返回的响应数据在data字段中,而非options,这会导致setOpts(options)设置为undefined,修正后:
const handleSearch = (id) => { axios .get(`https://jsonplaceholder.typicode.com/todos/${id}`) .then(({ data }) => { // 改为解构data setOpts([data]); // 单条todo是对象,需包装成数组符合options格式 }); };
4. 移除不必要的act包裹
react-testing-library的render方法已经自动处理了act逻辑,不需要手动用act包裹render调用。
完整修改后的测试代码
import { render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import axios from 'axios'; import { Description } from './Description'; describe("App Component Testing", () => { beforeEach(() => { jest.clearAllMocks(); }); it('should get typeahead data', async () => { const axiosGetSpy = jest.spyOn(axios, 'get').mockResolvedValue({ data: { id: 1, title: 'Test Todo' } }); const { queryByPlaceholderText } = render(<Description/>); const inputEl = queryByPlaceholderText(/Search todo by id./); userEvent.click(inputEl); await userEvent.type(inputEl, '1'); await waitFor(() => { expect(axiosGetSpy).toHaveBeenCalledTimes(1); expect(axiosGetSpy).toHaveBeenCalledWith('https://jsonplaceholder.typicode.com/todos/1'); }); }); });
内容的提问来源于stack exchange,提问作者Oam Psy
相关产品推荐
相关产品推荐

