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
相关产品推荐
相关产品推荐

