You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 14:47:09