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

Vitest中如何正确模拟Leaflet模块的多层链式调用(map方法及返回对象的setView方法)

Vitest中如何正确模拟Leaflet模块的多层链式调用(map方法及返回对象的setView方法)

我来帮你梳理一下当前写法的问题,然后给出两种可行的正确模拟方案,解决你遇到的困惑~

先说说你当前写法的几个问题

  1. 嵌套vi.mock是错误用法:你在vi.mock("leaflet")的工厂函数里又嵌套了vi.mock("leaflet.Map"),这不符合Vitest的mock规则——vi.mock是用来模拟整个模块的,嵌套会导致模块加载逻辑混乱,根本起不到你想要的效果。
  2. mock的结构和实际Leaflet不符:你让leaflet.default.map返回的是mapMock函数本身,但实际Leaflet中map()调用后返回的是一个带有setView方法的实例对象,不是函数。
  3. 错误的实例创建方式:你尝试new Map("")来获取实例,但你并没有正确模拟Leaflet.Map这个构造函数,自然会报错。

方案一:直接在模块mock中构建完整的链式结构

这种方式适合大多数场景,直接在顶层vi.mock里模拟出和Leaflet一致的结构:

import { vi, expect } from 'vitest';
import leaflet from 'leaflet';
// 导入你自己的业务模块
import { attachMap, MapConfiguration, GeographicCoordinates } from './your-module-path';

// 顶层mock Leaflet模块,Vitest会自动把这个mock提升到模块最顶端,确保导入leaflet时已经被mock
vi.mock('leaflet', () => {
  // 先创建setView的mock函数
  const mockSetView = vi.fn();
  // 创建map方法的mock函数,返回带有setView的实例对象
  const mockMap = vi.fn(() => ({
    setView: mockSetView
  }));

  // Leaflet的默认导出是一个包含map方法的对象,所以我们返回对应的结构
  return {
    default: {
      map: mockMap
    }
  };
});

describe('地图挂载测试', () => {
  beforeEach(() => {
    // 每次测试前重置所有mock的调用记录,避免测试间污染
    vi.clearAllMocks();
  });

  it("可以将地图挂载到DOM", () => {
    const config = new MapConfiguration(
      "map",
      new GeographicCoordinates(38.0, -90.0),
      15,
      19
    );
    attachMap(config);

    // 验证map方法是否被正确调用,参数是DOM元素的id"map"
    expect(leaflet.map).toHaveBeenCalledWith("map");
    // 验证setView是否被传入正确的坐标和缩放级别
    const mapInstanceMock = leaflet.map.mock.results[0].value;
    expect(mapInstanceMock.setView).toHaveBeenCalledWith([38.0, -90.0], 15);
  });
});

这个方案的要点:

  • 把vi.mock放在模块顶层,Vitest会自动处理模块加载顺序,保证你导入的leaflet已经是mock后的版本。
  • 完全复刻Leaflet的调用结构:leaflet.map()返回一个带setView的对象,完美匹配你的业务代码逻辑。
  • 用vi.clearAllMocks()在每次测试前重置mock状态,确保测试的独立性。

方案二:动态mock实例(更灵活的场景)

如果你的测试需要更灵活的控制(比如某个测试要让setView抛出错误,测试异常处理),可以先简单mock模块,再在beforeEach里动态生成实例:

import { vi, expect } from 'vitest';
import leaflet from 'leaflet';
import { attachMap, MapConfiguration, GeographicCoordinates } from './your-module-path';

// 先简单mock Leaflet的map方法为一个空的mock函数
vi.mock('leaflet', () => ({
  default: {
    map: vi.fn()
  }
}));

describe('地图挂载测试', () => {
  let mockSetView;
  let mockMapInstance;

  beforeEach(() => {
    // 每次测试前创建新的mock函数和实例
    mockSetView = vi.fn();
    mockMapInstance = { setView: mockSetView };
    // 指定leaflet.map调用后返回我们的mock实例
    leaflet.map.mockReturnValue(mockMapInstance);
    // 重置调用记录
    vi.clearAllMocks();
  });

  it("可以将地图挂载到DOM", () => {
    const config = new MapConfiguration(
      "map",
      new GeographicCoordinates(38.0, -90.0),
      15,
      19
    );
    attachMap(config);

    expect(leaflet.map).toHaveBeenCalledWith("map");
    expect(mockSetView).toHaveBeenCalledWith([38.0, -90.0], 15);
  });

  // 示例:测试setView抛出错误的场景
  it("处理setView抛出的错误", () => {
    // 修改mock让setView抛出错误
    mockSetView.mockImplementation(() => {
      throw new Error("地图加载失败");
    });

    const config = new MapConfiguration(
      "map",
      new GeographicCoordinates(38.0, -90.0),
      15,
      19
    );

    // 这里可以测试你的业务代码是否正确处理了这个错误
    expect(() => attachMap(config)).toThrow("地图加载失败");
  });
});

这个方案的优势:

  • 可以在不同测试中灵活修改mock的行为,满足多样化的测试场景。
  • 实例和mock函数都是每次测试重新创建,避免了测试间的状态污染。

最后再提醒几个常见坑

  • 不要在vi.mock的工厂函数里做嵌套mock,也不要在里面写测试逻辑,它只负责定义模块的mock结构。
  • 一定要保证mock的结构和实际模块一致,Leaflet的默认导出是一个对象,不是函数,这点很容易搞错。
  • 记得重置mock的调用记录,否则前一个测试的调用记录会影响后一个测试的结果。

备注:内容来源于stack exchange,提问作者Chris Schmitz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.22 14:28:06