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

基于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', []);
});
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 09:55:02