基于Jest为React TypeScript函数组件编写单元测试求助
React+TypeScript RCLogo组件单元测试方案(重点覆盖setupRegistry与getLogo)
前置准备
使用Jest + React Testing Library作为测试框架,若未安装依赖可执行:
npm install --save-dev jest @types/jest react-testing-library @testing-library/react @testing-library/jest-dom ts-jest
核心思路:模拟外部依赖
组件依赖多个外部模块,需先mock这些依赖,避免测试受外部逻辑干扰:
// __tests__/RCLogo.test.tsx import React from 'react'; import { render, act } from '@testing-library/react'; import RCLogo from '../RCLogo'; import { getMediaToken, isManifestRegistryItem, MediaRegistryHelper, SvgUtils, RegistryItem, SupportedRegistryMedia, } from 'common'; import { downloadMedia, loadMediaRegistry } from '../media-registry'; import { LogoAssetContext } from '../media-context'; import { of } from 'rxjs'; // Mock 所有外部依赖 jest.mock('common', () => ({ getMediaToken: jest.fn(), isManifestRegistryItem: jest.fn(), MediaRegistryHelper: jest.fn(() => ({ getRegistryMediaItem: jest.fn(), })), SvgUtils: { setSvgAttributes: jest.fn(), addA11ytoSVG: jest.fn(), }, })); jest.mock('../media-registry', () => ({ downloadMedia: jest.fn(), loadMediaRegistry: jest.fn(() => of(null)), // 默认返回空Observable })); // 测试用注册表配置 const mockRegistryConfig: RegistryItem<SupportedRegistryMedia>[] = [ { id: 'test-logo', type: 'svg', url: 'test.svg' }, ];
1. 测试setupRegistry函数
setupRegistry核心逻辑:接收注册表配置、合并配置、调用loadMediaRegistry并触发getLogo
测试用例1:传入有效配置时,正确调用loadMediaRegistry
test('setupRegistry调用loadMediaRegistry时传入正确的合并配置', async () => { render( <LogoAssetContext.Provider value={mockRegistryConfig}> <RCLogo token="test-token" srLabel="Test Logo" /> </LogoAssetContext.Provider> ); expect(loadMediaRegistry).toHaveBeenCalledWith('logo', mockRegistryConfig); });
测试用例2:配置为空/undefined时,传入空数组调用loadMediaRegistry
test('setupRegistry接收空配置时传入空数组调用loadMediaRegistry', async () => { render( <LogoAssetContext.Provider value={[]}> <RCLogo token="test-token" srLabel="Test Logo" /> </LogoAssetContext.Provider> ); expect(loadMediaRegistry).toHaveBeenCalledWith('logo', []); }); test('setupRegistry接收undefined配置时传入空数组调用loadMediaRegistry', async () => { render( <LogoAssetContext.Provider value={undefined}> <RCLogo token="test-token" srLabel="Test Logo" /> </LogoAssetContext.Provider> ); expect(loadMediaRegistry).toHaveBeenCalledWith('logo', []); });
测试用例3:loadMediaRegistry订阅完成后触发getLogo
test('setupRegistry在loadMediaRegistry完成后触发getLogo', async () => { const mockSvg = document.createElement('svg'); downloadMedia.mockReturnValue(of(mockSvg)); render( <LogoAssetContext.Provider value={mockRegistryConfig}> <RCLogo token="test-token" srLabel="Test Logo" /> </LogoAssetContext.Provider> ); await act(async () => {}); // 通过验证downloadMedia被调用,间接证明getLogo已触发 expect(downloadMedia).toHaveBeenCalled(); downloadMedia.mockRestore(); });
2. 测试getLogo函数
getLogo核心逻辑:处理token、获取媒体项、下载SVG、更新状态并调用SvgUtils方法
测试用例1:token为空时不执行任何逻辑
test('getLogo在token为空时不执行任何操作', async () => { render( <LogoAssetContext.Provider value={mockRegistryConfig}> <RCLogo token="" srLabel="Test Logo" /> </LogoAssetContext.Provider> ); expect(getMediaToken).not.toHaveBeenCalled(); expect(downloadMedia).not.toHaveBeenCalled(); });
测试用例2:token存在且为普通媒体项时,正确下载处理SVG
test('getLogo处理普通媒体项时正确下载并渲染SVG', async () => { // Mock 依赖返回值 (getMediaToken as jest.Mock).mockReturnValue({ scope: 'default' }); (MediaRegistryHelper.prototype.getRegistryMediaItem as jest.Mock).mockReturnValue({ id: 'test-logo', type: 'svg', url: 'test.svg', }); (isManifestRegistryItem as jest.Mock).mockReturnValue(false); const mockSvg = document.createElement('svg'); mockSvg.innerHTML = '<path d="M0 0" />'; (downloadMedia as jest.Mock).mockReturnValue(of(mockSvg)); const { container } = render( <LogoAssetContext.Provider value={mockRegistryConfig}> <RCLogo token="test-token" srLabel="Test Logo" /> </LogoAssetContext.Provider> ); await act(async () => {}); // 验证DOM渲染与工具方法调用 expect(container.firstChild).toHaveAttribute('aria-label', 'Test Logo'); expect(container.firstChild.innerHTML).toContain('<path d="M0 0" />'); expect(SvgUtils.setSvgAttributes).toHaveBeenCalledWith(mockSvg); expect(SvgUtils.addA11ytoSVG).toHaveBeenCalledWith( mockSvg, 'test-token', 'Test Logo', document ); });
测试用例3:处理manifest类型媒体项时正确响应Observable流
test('getLogo正确处理manifest类型的媒体项', async () => { (getMediaToken as jest.Mock).mockReturnValue({ scope: 'default' }); const mockManifestItem = { id: 'test-logo', type: 'manifest', data: of({ url: 'test.svg' }), }; (MediaRegistryHelper.prototype.getRegistryMediaItem as jest.Mock).mockReturnValue(mockManifestItem); (isManifestRegistryItem as jest.Mock).mockReturnValue(true); const mockSvg = document.createElement('svg'); (downloadMedia as jest.Mock).mockReturnValue(of(mockSvg)); render( <LogoAssetContext.Provider value={mockRegistryConfig}> <RCLogo token="test-token" srLabel="Test Logo" /> </LogoAssetContext.Provider> ); await act(async () => {}); expect(downloadMedia).toHaveBeenCalled(); });
测试用例4:downloadMedia返回undefined时打印日志
test('getLogo在downloadMedia返回undefined时打印日志', async () => { const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); (getMediaToken as jest.Mock).mockReturnValue({ scope: 'default' }); (MediaRegistryHelper.prototype.getRegistryMediaItem as jest.Mock).mockReturnValue({ id: 'test-logo' }); (downloadMedia as jest.Mock).mockReturnValue(undefined); render( <LogoAssetContext.Provider value={mockRegistryConfig}> <RCLogo token="test-token" srLabel="Test Logo" /> </LogoAssetContext.Provider> ); expect(consoleSpy).toHaveBeenCalledWith('Download Media Response is undefined for', 'test-token'); consoleSpy.mockRestore(); });
3. 组件集成测试
验证组件整体渲染逻辑:
test('RCLogo正确渲染aria-label与SVG内容', async () => { (getMediaToken as jest.Mock).mockReturnValue({ scope: 'default' }); (MediaRegistryHelper.prototype.getRegistryMediaItem as jest.Mock).mockReturnValue({ id: 'test-logo' }); const mockSvg = document.createElement('svg'); mockSvg.innerHTML = '<circle cx="50" cy="50" r="40" />'; (downloadMedia as jest.Mock).mockReturnValue(of(mockSvg)); const { getByLabelText } = render( <LogoAssetContext.Provider value={mockRegistryConfig}> <RCLogo token="test-token" srLabel="Test Logo" /> </LogoAssetContext.Provider> ); await act(async () => {}); const logoElement = getByLabelText('Test Logo'); expect(logoElement).toBeInTheDocument(); expect(logoElement.innerHTML).toContain('<circle cx="50" cy="50" r="40" />'); });
内容的提问来源于stack exchange,提问作者Payel Dutta
相关产品推荐
相关产品推荐

