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

React Testing Library测试Hook中脚本onload回调未触发问题

问题:动态插入script标签的onload/onerror回调在Jest测试中不触发

问题描述

开发支持Google身份服务的React库时,因官方登录脚本无npm安装包,需通过动态插入<script>标签的方式加载资源。在React Hook中实现了向文档头部插入script标签、绑定onload/onerror回调的逻辑,但测试时两个回调均未触发,已通过日志确认脚本插入函数完整执行。
测试输出中提示Ignored nodes: comments, <script />, <style />,怀疑和节点过滤配置有关,但未找到修改配置的明确方法。

相关代码

脚本插入函数

const loadGapiScript = (document, src, onLoad, onError) => {
  const script = document.getElementsByTagName('script')[0]

  let js = document.createElement('script')
  js.id = 'google-auth'
  js.src = src
  js.async = true
  js.defer = true

  js.onerror = onError
  js.onload = onLoad

  if (script && script.parentNode) {
    script.parentNode.insertBefore(js, script)
  } else {
    document.head.appendChild(js)
  }
}

测试代码

import { renderHook, waitFor } from '@testing-library/react'
import useGoogleLogin from './use-google-login'

describe('useGoogleLogin', () => {
  const clientId = 'somestring'
  const onLoad = jest.fn(() => {})

  it('calls the onLoad callback', async () => {
    renderHook(() => useGoogleLogin({ clientId, onLoad }))

    await waitFor(() => expect(onLoad).toHaveBeenCalled())
  })
})

测试报错输出

expect(jest.fn()).toHaveBeenCalled()

Expected number of calls: >= 1
Received number of calls:    0

Ignored nodes: comments, <script />, <style />
<html>
  <head />
  <body>
    <div />
  </body>
</html>

  30 |               renderHook(() => useGoogleLogin({ clientId, onLoad }))
  31 |
> 32 |               await waitFor(() => { expect(onLoad).toHaveBeenCalled() })
     |                                                       ^
  33 |             })
  34 |           })
  35 |         })

  at toHaveBeenCalled (__tests__/use-google-login-test.js:32:55)
  at runWithExpensiveErrorDiagnosticsDisabled (node_modules/@testing-library/dom/dist/config.js:50:12)
  at checkCallback (node_modules/@testing-library/dom/dist/wait-for.js:141:77)
  at checkRealTimersCallback (node_modules/@testing-library/dom/dist/wait-for.js:133:16)
  at Timeout.task [as _onTimeout] (node_modules/jsdom/lib/jsdom/browser/Window.js:514:19)

问题根因

测试输出中的Ignored nodes提示不是问题原因:这只是Testing Library打印错误诊断信息时,默认过滤脚本、样式、注释节点减少冗余输出的行为,和回调不触发没有任何关系,不需要修改这部分配置。
真正的原因是Jest默认使用的jsdom环境,默认不会实际加载动态插入script的远程src资源:jsdom只会把script节点插入DOM树,不会发起真实网络请求拉取脚本内容,自然不会触发onload或onerror回调,这是jsdom的默认设计,和业务代码逻辑无关。

解决方案

单元测试的核心是验证自身逻辑正确性,不需要真实请求Google的服务,直接手动模拟脚本加载结果即可:

  • 渲染Hook后,通过id获取到插入的script节点
  • 手动派发load或error事件,模拟脚本加载成功/失败的场景
  • 验证对应回调是否被调用

修改后的测试用例示例:

describe('useGoogleLogin', () => {
  const clientId = 'somestring'
  const onLoad = jest.fn(() => {})

  it('calls the onLoad callback when script loads successfully', async () => {
    renderHook(() => useGoogleLogin({ clientId, onLoad }))
    // 获取插入的脚本节点
    const gapiScript = document.getElementById('google-auth')
    // 手动触发load事件模拟加载成功
    gapiScript.dispatchEvent(new Event('load'))

    await waitFor(() => expect(onLoad).toHaveBeenCalled())
  })

  it('calls the onError callback when script fails to load', async () => {
    const onError = jest.fn()
    renderHook(() => useGoogleLogin({ clientId, onLoad, onError }))
    const gapiScript = document.getElementById('google-auth')
    // 手动触发error事件模拟加载失败
    gapiScript.dispatchEvent(new Event('error'))

    await waitFor(() => expect(onError).toHaveBeenCalled())
  })
})

如果是E2E测试需要真实加载脚本,可以在jsdom配置中开启resources: 'usable'选项允许加载外部资源,但这种方式依赖外部服务可用性、测试速度慢,完全不适合单元测试场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 22:21:46