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

Jest Mock Google Maps AdvancedMarkerElement遇非构造函数错误的解决方法

解决AdvancedMarkerElement Jest Mock时的"TypeError: AdvancedMarkerElement is not a constructor"问题

错误原因

你当前的mock逻辑存在核心问题:importAdvancedMarkerElement返回的是一个普通对象,但代码中需要用new关键字调用它——只有构造函数(可被new执行的函数)才能满足这个要求,普通对象无法被new实例化,因此抛出该错误。

正确Mock方案

1. 调整Mock逻辑,返回可实例化的构造函数

修改你的mock代码,让importAdvancedMarkerElement返回一个模拟的构造函数,该构造函数的实例包含你需要监听的addListener方法:

// 定义全局spy,方便测试中引用
const addListenerSpy = jest.fn();

jest.mock('pages/Dashboard/CriticalAndMajorAlertsMapCard/AdvancedMarkerElement', () => {
  // 模拟AdvancedMarkerElement构造函数,实例返回带addListener的对象
  const MockAdvancedMarkerElement = jest.fn(() => ({
    addListener: addListenerSpy
  }));

  return {
    // 用mockResolvedValue处理异步导入的promise
    importAdvancedMarkerElement: jest.fn().mockResolvedValue(MockAdvancedMarkerElement)
  };
});

2. 更新测试用例,适配新的Mock

移除原测试中针对旧google.maps.Marker的mock代码,直接使用全局的addListenerSpy来触发回调和断言:

/* global jest context */
import { render, waitFor } from '@testing-library/react'
import { initialize } from '@googlemaps/jest-mocks'
import '@googlemaps/react-wrapper'

import { useSessionContext } from 'hooks/SessionContext'
import Map from 'pages/Dashboard/CriticalAndMajorAlertsMapCard/Map'

// 全局spy,和mock中关联
const addListenerSpy = jest.fn();

jest.mock('hooks/SessionContext')
jest.mock('@googlemaps/markerclusterer', () => ({
  ...jest.requireActual('@googlemaps/markerclusterer'),
  MarkerClusterer: jest.fn().mockImplementation(() => {})
}))
jest.mock('react', () => ({
  ...jest.requireActual('react'),
  useLayoutEffect: jest.requireActual('react').useEffect
}))

// 调整mock AdvancedMarkerElement的代码(放在这里或顶部均可)
jest.mock('pages/Dashboard/CriticalAndMajorAlertsMapCard/AdvancedMarkerElement', () => {
  const MockAdvancedMarkerElement = jest.fn(() => ({
    addListener: addListenerSpy
  }));

  return {
    importAdvancedMarkerElement: jest.fn().mockResolvedValue(MockAdvancedMarkerElement)
  };
});

// 测试数据保持不变
const dealer = Object.freeze({
  dealerId: 1,
  latitude: 40.15,
  longitude: -111.11
})

const devices = Object.freeze({
  deviceId: '12345ABCD',
  criticalAlertCount: 1,
  majorAlertCount: 1,
  sdaMajorAlertCount: 0
})

const customersWithLocation = Object.freeze(
  [{
    customerId: 1,
    dealerId: 2,
    latitude: 35.01,
    longitude: -113.45,
    devices: [devices]
  }]
)

describe('Map', () => {
  context('when map loads', () => {
    const openInfoWindowSpy = jest.fn()
    const setContentinfoWindowSpy = jest.fn()

    beforeEach(() => {
      // 重置所有spy
      addListenerSpy.mockReset();
      openInfoWindowSpy.mockReset();
      setContentinfoWindowSpy.mockReset();

      useSessionContext.mockReturnValue({
        session: dealer
      })

      initialize()
      window.google.maps.InfoWindow = jest.fn().mockImplementation(() => {
        return {
          setContent: setContentinfoWindowSpy,
          open: openInfoWindowSpy
        }
      })
    })

    describe('when user clicks on the customer marker', () => {
      it('opens info window for customer ', async () => {
        render(<Map customersWithLocation={customersWithLocation} />)

        // 触发marker的点击回调
        addListenerSpy.mock.calls[0][1]()

        await waitFor(() => {
          expect(setContentinfoWindowSpy).toHaveBeenCalledTimes(1)
          expect(openInfoWindowSpy).toHaveBeenCalledTimes(1)
        })
      })
    })
  })
})

关键注意点

  • 异步处理:用mockResolvedValue确保异步导入的promise立即resolve,避免测试中出现异步等待问题
  • 构造函数模拟:必须让mock返回一个可被new执行的函数,而不是普通对象
  • Spy复用:将addListenerSpy定义为全局变量,方便在mock和测试用例中统一引用,避免重复定义

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 20:04:55