React TypeScript自定义Hook测试求助:useFetch测试实现
测试自定义useFetch Hook的正确方法
问题背景
我正在学习React测试,需要测试一个自定义的useFetch Hook,但完全不知道该怎么操作。
自定义useFetch Hook代码
import { useState, useCallback } from 'react'; import axios from 'axios'; export const useFetch = () => { const [error, setError] = useState(null); const [loading, setLoading] = useState(false); const fetchData = useCallback(async (url: string) => { setLoading(true); try { const response = await axios.get(url); const data = await response.data.data; setLoading(false); return data; } catch (error: any) { if (error.name !== 'AbortError') { setLoading(false); setError(error.message); } } }, []); return { error, loading, fetchData }; };
我尝试的测试代码(无效)
import { useFetch } from '../../hooks/useFetch'; import mockAxios from 'jest-mock-axios'; import axios from 'axios'; import { renderHook } from '@testing-library/react-hooks'; jest.mock('axios'); describe('useFetch', () => { afterEach(() => { mockAxios.reset(); }); it('fetches successfully data from an API', async () => { const { result,waitFor } = renderHook(() => useFetch()); let responseObj = { data: 'test response' }; mockAxios.mockResponseFor({ url: '/get' }, responseObj); expect(result.current.error).toBe(null); await expect(result.current.fetchData('react')).resolves.toBe(responseObj); }); });
修正后的测试方案
你的测试代码存在几处问题:混用了jest.mock('axios')和第三方mockAxios库、Mock响应结构与Hook中response.data.data的取值逻辑不匹配、未正确处理状态更新后的异步断言。以下是正确的测试实现及说明:
1. 依赖准备
确保已安装必要测试依赖:
npm install --save-dev @testing-library/react-hooks jest axios @types/jest @types/react
2. 完整测试代码
import { useFetch } from '../../hooks/useFetch'; import axios from 'axios'; import { renderHook, waitFor } from '@testing-library/react-hooks'; // 用Jest直接Mock axios,无需额外库 jest.mock('axios'); // TypeScript类型断言,识别Mock后的axios实例 const mockedAxios = axios as jest.Mocked<typeof axios>; describe('useFetch Hook', () => { // 每个测试用例执行后重置Mock afterEach(() => { jest.clearAllMocks(); }); it('should set loading to true, then false, and return data on successful fetch', async () => { // 定义匹配Hook取值逻辑的Mock响应 const mockData = 'test data'; mockedAxios.get.mockResolvedValue({ data: { data: mockData } }); const { result } = renderHook(() => useFetch()); // 初始状态断言 expect(result.current.loading).toBe(false); expect(result.current.error).toBe(null); // 触发请求 const fetchPromise = result.current.fetchData('https://example.com/api'); // 调用后立即检查loading状态 expect(result.current.loading).toBe(true); // 等待状态更新完成,断言最终结果 await waitFor(() => { expect(result.current.loading).toBe(false); }); const fetchedData = await fetchPromise; expect(fetchedData).toBe(mockData); expect(result.current.error).toBe(null); // 验证axios.get的调用参数 expect(mockedAxios.get).toHaveBeenCalledWith('https://example.com/api'); }); it('should set loading to false and error message on fetch failure (non-AbortError)', async () => { const mockError = new Error('Network Error'); mockedAxios.get.mockRejectedValue(mockError); const { result } = renderHook(() => useFetch()); // 触发请求 await result.current.fetchData('https://example.com/api'); // 断言错误状态 await waitFor(() => { expect(result.current.loading).toBe(false); expect(result.current.error).toBe('Network Error'); }); expect(mockedAxios.get).toHaveBeenCalledWith('https://example.com/api'); }); it('should not set error when error is AbortError', async () => { const abortError = new Error('Aborted'); abortError.name = 'AbortError'; mockedAxios.get.mockRejectedValue(abortError); const { result } = renderHook(() => useFetch()); await result.current.fetchData('https://example.com/api'); // 断言loading重置但error保持null await waitFor(() => { expect(result.current.loading).toBe(false); expect(result.current.error).toBe(null); }); }); });
关键修正说明
- 统一Mock方式:直接使用Jest原生的
jest.mock('axios')进行Mock,避免与第三方Mock库冲突。 - 匹配响应结构:Hook中通过
response.data.data获取数据,所以Mock响应必须嵌套data字段,确保取值逻辑一致。 - 异步状态断言:使用
waitFor等待Hook的状态更新完成后再执行断言,因为React状态更新是异步的。 - 覆盖所有分支:测试了成功请求、普通错误、AbortError三种场景,覆盖Hook的全部逻辑。
内容的提问来源于stack exchange,提问作者ludinj
相关产品推荐
相关产品推荐

