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

使用Jest和React Testing Library测试含ua-parser-js的React组件

问题场景

我实现了一个根据用户操作系统(iOS/Android)展示对应应用商店下载入口的React组件:

import { UAParser } from "ua-parser-js";

export const DownloadApp = ({ appleStoreUrl, playStoreUrl }: DownloadAppProps): JSX.Element => {
  const parser = new UAParser();
  const os = parser.getOS();

  return (
    <div className="flex h-12">
      {os.name === "iOS" && (
        <div>
          <a href={appleStoreUrl} title="Apple App Store">
            <AppleAppStore height="48" />
          </a>
        </div>
      )}

      {(os.name === "Android" || os.name === "Android[-x86]") && (
        <div className="shrink">
          <a href={playStoreUrl} title="Google Play Store">
            <GooglePlayStore height="48" />
          </a>
        </div>
      )}
    </div>
  );
};

手动测试组件功能正常,但用Jest + React Testing Library写单元测试时,尝试mock ua-parser-js库,让getOS()返回指定的iOS/Android系统标识,mock始终无效,实际调用原库返回undefined,该如何解决?

解决方案

以下几种mock方式可以解决这个问题:

方式1:Mock整个UAParser类及实例方法

在测试文件中直接mock ua-parser-js库,替换UAParser类的实现,让它的实例返回预设的getOS()结果:

import { render, screen } from '@testing-library/react';
import { DownloadApp } from './DownloadApp';

// Mock ua-parser-js 库
jest.mock('ua-parser-js', () => {
  return {
    UAParser: jest.fn().mockImplementation(() => {
      return {
        getOS: jest.fn().mockReturnValue({ name: 'iOS' }) // 可替换为Android
      };
    })
  };
});

test('渲染iOS应用商店入口', () => {
  render(<DownloadApp appleStoreUrl="https://apple.com/app" playStoreUrl="https://play.google.com/app" />);
  expect(screen.getByTitle('Apple App Store')).toBeInTheDocument();
});

test('渲染Android应用商店入口', () => {
  // 重新mock返回Android
  jest.mock('ua-parser-js', () => {
    return {
      UAParser: jest.fn().mockImplementation(() => {
        return {
          getOS: jest.fn().mockReturnValue({ name: 'Android' })
        };
      })
    };
  });
  
  render(<DownloadApp appleStoreUrl="https://apple.com/app" playStoreUrl="https://play.google.com/app" />);
  expect(screen.getByTitle('Google Play Store')).toBeInTheDocument();
});

方式2:动态修改mock返回值

如果需要在同一个测试文件中切换不同的OS返回值,可以先创建可修改的mock函数,再动态调整结果:

import { render, screen } from '@testing-library/react';
import { DownloadApp } from './DownloadApp';
import { UAParser } from 'ua-parser-js';

// 先mock UAParser类
jest.mock('ua-parser-js');
const mockGetOS = jest.fn();

// 让UAParser实例返回mock的getOS方法
(UAParser as jest.Mock).mockImplementation(() => {
  return { getOS: mockGetOS };
});

test('切换渲染不同系统的下载入口', () => {
  // 测试iOS场景
  mockGetOS.mockReturnValue({ name: 'iOS' });
  render(<DownloadApp appleStoreUrl="https://apple.com/app" playStoreUrl="https://play.google.com/app" />);
  expect(screen.getByTitle('Apple App Store')).toBeInTheDocument();
  expect(screen.queryByTitle('Google Play Store')).not.toBeInTheDocument();

  // 测试Android场景
  mockGetOS.mockReturnValue({ name: 'Android' });
  render(<DownloadApp appleStoreUrl="https://apple.com/app" playStoreUrl="https://play.google.com/app" />);
  expect(screen.getByTitle('Google Play Store')).toBeInTheDocument();
  expect(screen.queryByTitle('Apple App Store')).not.toBeInTheDocument();
});

方式3:使用jest.spyOn针对getOS方法mock

如果不想完全替换整个类,也可以用jest.spyOn来mock实例的getOS方法:

import { render, screen } from '@testing-library/react';
import { DownloadApp } from './DownloadApp';
import { UAParser } from 'ua-parser-js';

test('mock iOS系统', () => {
  // 创建UAParser实例的spy
  const mockGetOS = jest.spyOn(UAParser.prototype, 'getOS').mockReturnValue({ name: 'iOS' });
  
  render(<DownloadApp appleStoreUrl="https://apple.com/app" playStoreUrl="https://play.google.com/app" />);
  expect(screen.getByTitle('Apple App Store')).toBeInTheDocument();
  
  // 清理mock
  mockGetOS.mockRestore();
});
常见失效原因

之前mock无效通常是以下原因导致:

  • 路径不匹配:确保jest.mock的路径和组件中import的路径完全一致(比如是否用了别名、相对路径错误)
  • mock逻辑错误:只mock了UAParser类本身,没有覆盖实例的getOS方法
  • import顺序问题:确保jest.mock语句在import组件之前执行(Jest会自动提升mock到顶部,但手动调整顺序更稳妥)

内容的提问来源于stack exchange,提问作者Tom Maton

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 00:01:07