React TypeScript组件测试:axios-mock-adapter无法返回模拟数据
React组件测试:axios-mock-adapter模拟API未生效问题
问题描述
我在测试一个基于Axios发起请求的React TypeScript组件,尝试用axios-mock-adapter模拟API调用,但运行测试时,组件并未遍历comments状态中的模拟数据。组件通过自定义Hook useCommentsDataFetch结合useEffect从后端API获取数据。
测试代码(Comments.test.tsx)
import React from 'react'; import { render, fireEvent, screen, waitFor } from '@testing-library/react'; import { Comments } from '../components/Comments'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; describe('RiskPatternCard', () => { beforeEach(() => { }); test('renders the Comments component', () => { render(<Comments className="test-class" />); expect(screen.getByTestId('comments_header')).toBeInTheDocument(); }); test('displays the correct number of comments', () => { const mockedData = [ { id: 1, data: { title: 'Comment 1', body: 'Body 1' }, userObject: { fullname: 'User 1' } }, { id: 2, data: { title: 'Comment 2', body: 'Body 2' }, userObject: { fullname: 'User 2' } }, { id: 3, data: { title: 'Comment 3', body: 'Body 3' }, userObject: { fullname: 'User 3' } }, ]; const mock = new MockAdapter(axios); // const comments = [{ id: 1, comment: 'Test comment' }]; mock.onGet('http://localhost:8000/comments?case_id=123').reply(200, mockedData); const { getByTestId } = render(<Comments className="test-class" />); waitFor(() => expect(getByTestId('comments-header')).toBeInTheDocument()); const comment = getByTestId('comments_header'); expect(comment).toBeInTheDocument(); expect(comment).toHaveTextContent('Comments'); expect(screen.getByTestId('comment_fullname')).toBeInTheDocument(); }); })
组件代码(Comments.tsx)
export const Comments: React.FC<{ className: string }> = ({className}) => { const {caseId, hasAccess} = useRiskPatternDetailsPageContext(); const [openDialog, setOpenDialog] = useState<boolean>(false); const [comments, setComments] = useCommentsDataFetch(caseId); const {currentUser} = useAuth(); return ( <> <div className={`card ${className}`}> {/* begin::Header */} [code...] </div> {/* end::Header */} {/* begin::Body */} <div className='card-body py-3'> <div className='tab-content'> {/* begin::Tap pane */} <div className='tab-pane fade show active' id='kt_table_widget_7_tab_1'> {/* begin::Table container */} <div className='table-responsive' style={{height: 180}}> {/* begin::Table */} <table className='table overflow-auto align-middle gs-0 gy-3'> {/* begin::Table head */} <thead> <tr> <th className='p-0 w-50px'></th> <th className='p-0 min-w-150px'></th> </tr> </thead> {/* end::Table head */} {/* begin::Table body */} <tbody> {comments && comments.map((comment, index) => ( <tr key={comment.id ? comment.id : index}> <td> <div className='symbol symbol-50px'> { comment.userObject?.pic ? <img src={toAbsoluteUrl(comment.userObject?.pic)} alt={comment.userObject && comment.userObject.fullname}/> : <img src={toAbsoluteUrl('/media/svg/avatars/blank.svg')} alt={comment.userObject && comment.userObject.fullname}/> } </div> </td> <td> <a href='src/app/generel-modules/RiskPatternsTableModule/components#' className='text-dark fw-bolder text-hover-primary mb-1 fs-6' data-cy='comment_fullname' data-testid='comment_fullname'> {comment.userObject?.fullname} - {comment.data.title} </a> <span className='text-muted fw-bold d-block fs-8'>Created - {comment.convertedTimestamp}</span> <span className='text-dark fw-bold d-block fs-7' data-cy='comment_body'> {comment.data.body} </span> </td> </tr> ))} </tbody> {/* end::Table body */} </table> </div> {/* end::Table */} </div> {/* end::Tap pane */} </div> </div> {/* end::Body */} </div> </> ) }
问题原因及修复方案
1. Context未提供正确的caseId
组件从useRiskPatternDetailsPageContext获取caseId,但测试时未注入该Context,导致实际请求的URL中case_id参数不是123,和mock的URL不匹配,模拟数据无法返回。
修复:测试时包裹Context Provider,传入指定的caseId:
// 替换为你的Context实际路径 import { RiskPatternDetailsPageContext } from '../contexts/RiskPatternDetailsPageContext'; render( <RiskPatternDetailsPageContext.Provider value={{caseId: 123, hasAccess: true}}> <Comments className="test-class" /> </RiskPatternDetailsPageContext.Provider> );
2. waitFor未正确等待数据加载
当前代码中waitFor未使用await,无法等待异步请求完成;且断言的是header元素,而非评论数据加载完成的状态。
修复:用await等待评论元素加载完成:
await waitFor(() => { expect(screen.getAllByTestId('comment_fullname')).toHaveLength(3); });
3. 重复的testId导致断言失败
每个评论的a标签都使用了同一个testId="comment_fullname",使用getByTestId会因为找到多个元素报错,需改用getAllByTestId获取所有评论元素。
4. 未清理axios mock
测试后未重置mock,可能影响其他测试用例的运行。
修复:在afterEach中重置mock:
let mock: MockAdapter; beforeEach(() => { mock = new MockAdapter(axios); }); afterEach(() => { mock.reset(); });
修正后的完整测试代码
import React from 'react'; import { render, screen, waitFor } from '@testing-library/react'; import { Comments } from '../components/Comments'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { RiskPatternDetailsPageContext } from '../contexts/RiskPatternDetailsPageContext'; // 替换为你的Context路径 describe('Comments Component', () => { let mock: MockAdapter; beforeEach(() => { mock = new MockAdapter(axios); }); afterEach(() => { mock.reset(); }); test('renders the Comments component', () => { render( <RiskPatternDetailsPageContext.Provider value={{caseId: 123, hasAccess: true}}> <Comments className="test-class" /> </RiskPatternDetailsPageContext.Provider> ); expect(screen.getByTestId('comments_header')).toBeInTheDocument(); }); test('displays the correct number of comments', async () => { const mockedData = [ { id: 1, data: { title: 'Comment 1', body: 'Body 1' }, userObject: { fullname: 'User 1' }, convertedTimestamp: '2024-01-01' }, { id: 2, data: { title: 'Comment 2', body: 'Body 2' }, userObject: { fullname: 'User 2' }, convertedTimestamp: '2024-01-02' }, { id: 3, data: { title: 'Comment 3', body: 'Body 3' }, userObject: { fullname: 'User 3' }, convertedTimestamp: '2024-01-03' }, ]; mock.onGet('http://localhost:8000/comments?case_id=123').reply(200, mockedData); render( <RiskPatternDetailsPageContext.Provider value={{caseId: 123, hasAccess: true}}> <Comments className="test-class" /> </RiskPatternDetailsPageContext.Provider> ); // 等待评论加载完成并验证内容 await waitFor(() => { const commentNames = screen.getAllByTestId('comment_fullname'); expect(commentNames).toHaveLength(3); expect(commentNames[0]).toHaveTextContent('User 1 - Comment 1'); expect(commentNames[1]).toHaveTextContent('User 2 - Comment 2'); expect(commentNames[2]).toHaveTextContent('User 3 - Comment 3'); }); }); });
内容的提问来源于stack exchange,提问作者Frederik Nielsen
相关产品推荐
相关产品推荐

