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

Vitest测试全通过却抛出未捕获异常求助(ApexCharts+React)

环境版本

  • Vitest: 0.34.4
  • apexcharts: ~3.41.0
  • react-apexcharts: ~1.4.1
  • React: ~18.2.0
  • Ubuntu 22.04

问题描述

所有测试用例执行成功,但控制台仍抛出未捕获异常。尝试过dangerouslyIgnoreUnhandledErrors、组件unmount等方案,均无效。当前测试React项目中的ApexCharts组件,求能跳过该异常的解决思路。

错误日志

✓ src/react-ui/test/components/DeleteModal.test.tsx (4)
 ✓ src/react-ui/test/components/TableIdsSelectable.test.tsx (1)
 ✓ src/react-ui/test/components/TableRadio.test.tsx (1)
 ✓ src/react-ui/test/components/TableNotClickable.test.tsx (1)
 ✓ src/react-ui/test/components/TableCheckboxes.test.tsx (1) 425ms
 ✓ src/react-ui/test/components/Text.test.tsx (2)
 ✓ src/react-ui/test/components/Select.test.tsx (3) 477ms
 ✓ src/react-ui/test/components/Number.test.tsx (2)
 ✓ src/test/components/SubscriptionThickBarChart.test.tsx (1)

Uncaught Exception 
TypeError: Cannot read properties of undefined (reading '0')
 ❯ t node_modules/apexcharts/dist/apexcharts.common.js:10:9056
 ❯ Et.a.PathArray.morph node_modules/apexcharts/dist/apexcharts.common.js:10:12451
 ❯ create.initAnimations node_modules/apexcharts/dist/apexcharts.common.js:6:422898
 ❯ create.startCurrent node_modules/apexcharts/dist/apexcharts.common.js:6:422208
 ❯ create.start node_modules/apexcharts/dist/apexcharts.common.js:6:422007
 ❯ create.<anonymous> node_modules/apexcharts/dist/apexcharts.common.js:6:426560
 ❯ listOnTimeout node:internal/timers:559:17
 ❯ processTimers node:internal/timers:502:7

  Test Files  9 passed (9)
      Tests  16 passed (16)
     Errors  1 error
   Start at  12:59:57
   Duration  3.32s (transform 770ms, setup 853ms, collect 12.86s, tests 2.14s, environment 3.08s, prepare 1.28s)

测试代码

/* eslint-disable @typescript-eslint/no-empty-function */
import React from 'react';
import * as LocalQuery from '../../generated/RestUtilities2';
import * as LocalCurrentId from '../../utils/useCurrentId';
import { _MockSelector } from '../../react-ui/test/components/_MockRedux';
import { afterEach, beforeAll, describe, it, vi, expect } from 'vitest';
import ApexCharts from 'apexcharts';
import { render, waitFor } from '@testing-library/react';
import { BaseThickBarChart } from '../../components/charts/SubscriptionThickBarChart';
import '@testing-library/jest-dom';

vi.mock('apexcharts', async () => {
  const actual = (await vi.importActual('apexcharts')) as object;
  return {
    ...actual,
    exec: vi.fn(() => {
      return new Promise((resolve) => {
        resolve('uri');
      });
    })
  };
});

Object.defineProperty(window, 'ResizeObserver', {
  writable: true,
  value:
    window.ResizeObserver ||
    vi.fn().mockImplementation(() => ({
      observe: vi.fn(),
      unobserve: vi.fn(),
      disconnect: vi.fn()
    }))
});

Object.defineProperty(global.SVGElement.prototype, 'getScreenCTM', {
  writable: true,
  value: vi.fn(() => 10)
});

Object.defineProperty(global.SVGElement.prototype, 'getBBox', {
  writable: true,
  value: vi.fn().mockReturnValue({
    x: 10,
    y: 10
  })
});

Object.defineProperty(global.SVGElement.prototype, 'getComputedTextLength', {
  writable: true,
  value: vi.fn().mockReturnValue(10)
});

Object.defineProperty(global.SVGElement.prototype, 'createSVGMatrix', {
  writable: true,
  value: vi.fn().mockReturnValue({
    x: 10,
    y: 10,
    inverse: () => 'what',
    multiply: () => 'hej'
  })
});

window.ResizeObserver = vi.fn().mockImplementation(() => ({
  disconnect: vi.fn(),
  observe: vi.fn(),
  unobserve: vi.fn()
}));

ApexCharts.prototype.destroy = vi.fn();

describe('Table Range chart', () => {
  try {
    beforeAll(() => {
      global.ResizeObserver = class ResizeObserver {
        observe() {}
        unobserve() {}
        disconnect() {}
      };
    });

    afterEach(() => {
      vi.restoreAllMocks();
    });

    vi.mock('react-redux', () => ({
      ...vi.importActual('react-redux'),
      useSelector: vi.fn(() => _MockSelector)
    }));
    vi.spyOn(LocalQuery, 'useQuery').mockReturnValue({
      call: () =>
        new Promise((resolve) =>
          resolve({
            status: 200,
            content: [
              {
                name: 'plan1',
                partNumber: 'part-plan1',
                price: 1000.0,
                currency: 0,
                validFrom: '2024-09-27T22:21:27.782Z',
                validTo: '2024-10-27T22:21:27.782Z',
                status: 0,
                id: 'AgLD'
              }
            ]
          })
        ),
      data: {
        status: 200
      },
      loading: false,
      refetch: () => new Promise((resolve) => resolve())
    });
    vi.spyOn(LocalCurrentId, 'useCurrentId').mockReturnValue('');
  } catch (error) {
    expect(error).toBeInstanceOf(TypeError);
    expect(error).toHaveProperty('message', 'Cannot read properties of undefined');
  }

  it('Checking SubscriptionThickBarChart', async () => {
    try {
      const { container, rerender, unmount } = render(<BaseThickBarChart buttons={[{ title: '30 days', value: 1 }]} />);
      await waitFor(() => rerender(<BaseThickBarChart buttons={[{ title: '30 days', value: 1 }]} />));
      const chartSubscription = container?.querySelector('path[val=\"1730067687782\"]');
      expect(chartSubscription).toBeInTheDocument();
      unmount();
    } catch (error) {
      expect(error).toBeInstanceOf(TypeError);
      console.log('error :>> ', error);
      // expect(error).toHaveProperty('message', 'Cannot read properties of undefined');
    }
  });
});

解决思路

从错误栈来看,异常来自ApexCharts内部的动画初始化逻辑(initAnimations -> PathArray.morph),属于测试完成后异步触发的未捕获异常,以下是几种可行的处理方案:

1. 禁用ApexCharts动画

在测试时给图表组件传递animations: { enabled: false }的配置,从根源避免动画相关代码执行:

// 渲染组件时添加动画禁用配置
render(<BaseThickBarChart 
  buttons={[{ title: '30 days', value: 1 }]} 
  chartOptions={{ animations: { enabled: false } }} 
/>);

如果BaseThickBarChart内部封装了配置,可在测试前全局覆盖ApexCharts默认配置:

beforeAll(() => {
  ApexCharts.defaultOptions = {
    ...ApexCharts.defaultOptions,
    animations: { enabled: false }
  };
});

2. 彻底Mock动画相关方法

当前Mock仅覆盖了exec和destroy,需补充Mock动画初始化逻辑,避免进入出错分支:

vi.mock('apexcharts', async () => {
  const actual = await vi.importActual('apexcharts');
  return {
    ...actual,
    exec: vi.fn(() => Promise.resolve('uri')),
    initAnimations: vi.fn(),
    start: vi.fn()
  };
});

// 或直接Mock实例方法
ApexCharts.prototype.initAnimations = vi.fn();
ApexCharts.prototype.start = vi.fn();

3. 在Vitest中过滤特定异常

在测试文件顶部添加全局异常捕获,忽略目标异常:

beforeAll(() => {
  process.on('uncaughtException', (error) => {
    if (error.message.includes("Cannot read properties of undefined (reading '0')") && error.stack.includes('apexcharts.common.js')) {
      return;
    }
    throw error;
  });
});

也可在vitest.config.js中全局配置:

export default defineConfig({
  test: {
    unhandledErrors: {
      ignore: [/Cannot read properties of undefined \(reading '0'\)/]
    }
  }
});

4. 等待所有异步任务完成

启用假定时器,在测试末尾等待所有定时器执行完毕,确保动画异步代码处理完成:

beforeAll(() => {
  vi.useFakeTimers();
});

afterAll(() => {
  vi.useRealTimers();
});

it('Checking SubscriptionThickBarChart', async () => {
  const { container, rerender, unmount } = render(<BaseThickBarChart buttons={[{ title: '30 days', value: 1 }]} />);
  await waitFor(() => rerender(<BaseThickBarChart buttons={[{ title: '30 days', value: 1 }]} />));
  const chartSubscription = container?.querySelector('path[val=\"1730067687782\"]');
  expect(chartSubscription).toBeInTheDocument();
  
  await vi.runAllTimers();
  unmount();
});

内容的提问来源于stack exchange,提问作者x-magix

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 00:03:10