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

Jest中toBeCalledWith匹配函数传入的JSX.Element不匹配问题

问题:Mock React组件函数时匹配JSX.Element失败

背景

我们有一个内部UI库,其中的toast函数负责渲染传入的JSX.Element。原本用React Testing Library做测试,但该库存在bug(UI运行正常,已提交维护团队修复),等待修复期间我mock了这个函数,但匹配预期JSX.Element时始终失败。

React组件代码

import { toast } from '@company/alerts/toast'

export const ToastTest = ({title}: {title: string}) => {
   toast(<h1> {title} </h1>)

    return <div testId='toast-location-div'></div>
}

初始测试代码

import React from 'react'
import { ToastTest } from './toast-test'
import { toast } from '@company/alerts/toast'
import { render, waitFor } from 'react-testing-library'

jest.mock('@company/alerts', () => {
   ...jest.requireActual('@company/alerts'),
   toast: jest.fn()
})

const TestTitleComponent = ({title}) => <h1> {title} </h1>

describe('Toast test', () => {
   it('should call toast', async () => {
      render(<ToastTest title="foo" />)

      await waitFor(() => {
         expect(toast).toHaveBeenCalledWith(<TestTitleComponent title="foo" />)
      })
   })
})

初始报错信息

expect(jest.fn()).toHaveBeenCalledWith(...expected)
    - Expected
    + Received

    - <h1> foo </h1>
    + <h1> foo </h1>

看起来内容完全一致,但实际是React元素的内部引用/属性不同,导致匹配失败。

后续尝试的问题

按照建议改用spy获取调用参数,尝试多种匹配方式均失败:

  1. 使用toStrictEqual匹配完整元素:
expect(toastSpy.mock.calls[0][0]).toStrictEqual(
         <h1> foo </h1>
       )

报错:

Expected: <h1> foo </h1>
     Received: serializes to the same string
  1. 使用expect.objectContaining匹配元素结构:
await waitFor(() => {
       expect(toastSpy).toHaveBeenCalled()
       expect(expect.objectContaining(toastSpy.mock.calls[0][0])).toEqual(
         expect.objectContaining(
           <h1> foo </h1>
         )
       )
     })

报错显示元素内部属性(如_owner)差异过大,无法匹配。
3. 尝试直接访问props时遇到TypeScript错误:

expect(toastSpy.mock.calls[0][0]['props']).toEqual({
         title: 'foo'
       })

报错:

src/banner/components/banner-toast.test.tsx:39:40 - error TS2304: Cannot find name 'props'.

最后用Object.values()取第4位的props勉强成功,但这种方法非常不优雅。

解决方案

方案1:类型断言解决TS问题,直接匹配props

不用hack式的索引,给React元素做类型断言,直接访问props:

await waitFor(() => {
  expect(toastSpy).toHaveBeenCalled();
  const toastArg = toastSpy.mock.calls[0][0] as React.ReactElement<{title: string}>;
  expect(toastArg.props).toEqual({ title: 'foo' });
});

既解决了TypeScript的类型报错,又能精准匹配关键的props内容,避免因React元素内部属性差异导致的匹配失败。

方案2:拆解元素关键属性验证

在断言时分别验证元素的类型和内容,不依赖完整元素匹配:

it('should call toast with correct title', async () => {
  render(<ToastTest title="foo" />);

  await waitFor(() => {
    expect(toast).toHaveBeenCalled();
    const [receivedElement] = toast.mock.calls[0];
    // 验证元素类型是h1
    expect(receivedElement.type).toBe('h1');
    // 验证子内容包含目标文本
    expect(receivedElement.props.children).toContain('foo');
  });
});

这种方式更灵活,能同时确认元素类型和内容是否符合预期。

方案3:调整mock逻辑,用RTL查询DOM元素

如果可以修改mock让toast把元素渲染到测试容器里,就可以用RTL的查询方式验证,更贴近用户视角:

// 修改mock,让toast把元素挂载到组件返回的容器中
jest.mock('@company/alerts', () => ({
  ...jest.requireActual('@company/alerts'),
  toast: (element: React.ReactElement) => {
    const container = document.getElementById('toast-location-div');
    if (container) {
      ReactDOM.render(element, container);
    }
  }
}));

it('should render toast with correct title', async () => {
  render(<ToastTest title="foo" />);
  await waitFor(() => {
    expect(screen.getByText('foo')).toBeInTheDocument();
  });
});

这种方式符合RTL的测试理念,关注用户能看到的实际内容,而非内部函数调用细节。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 19:33:10