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

使用Vitest Mock Kysely的MySQL2数据库实例时遇vi.mock报错

解决Vitest Mock Kysely实例时的"If you are using 'vi.mock' factory, make sure there are no top level variables inside"错误

问题背景

作为测试新手,尝试用Vitest将mysql2数据库Mock为Kysely实例时,触发错误:

If you are using "vi.mock" factory, make sure there are no top level variables inside

涉及代码如下:

database.ts

import * as dotenv              from "dotenv";
import { type DB }              from "../types/schema.types";
import { createPool, }          from "mysql2";
import { Kysely, MysqlDialect } from "kysely";
dotenv.config();

export const dialect = new MysqlDialect({
  pool: createPool({
    database: `${process.env.DATABASE}`,
    host: `${process.env.DATABASE_HOST}`,
    port: process.env.DATABASE_PORT as unknown as number,
    user: `${process.env.USER}`,
    password: process.env.PASSWORD,
    waitForConnections: true,
    multipleStatements: true,
    charset: "utf8mb4",
    connectionLimit: `${process.env.DATABASE_CONNECTION_LIMIT}` as unknown as number,
    queueLimit: 0,
  }),
})

const db = new Kysely<DB>({ dialect });
export default db;

原测试代码sample.spec.ts

import { it, vi, expect } from "vitest";
import db from "../database/database";
import { DB } from "../types/schema.types";
import { Kysely, MysqlDialect } from "kysely";

// Test case
it("Get user count", async () => {
  const mocks = vi.hoisted(() => ({ query: vi.fn() }));
  
  // Mocking database module
  vi.mock("../database/database", async (importOriginal) => {
    const actualModule = await importOriginal<typeof import("../database/database")>();
    return {
      ...actualModule,
      __esModule: true,
      ownerDb: new Kysely<DB>({
        dialect: new MysqlDialect({ pool: vi.fn() }),
      }),
    };
  });

  mocks.query.mockResolvedValue({ rows: [{ count: 1 }] });

  const query = db
    .selectFrom("users")
    .select((eb) => eb.fn.count<number>("users.user_id").as("count"));

  const { count } = await db
    .selectNoFrom((eb) => eb.fn.coalesce(query, eb.lit(0)).as("count"))
    .executeTakeFirstOrThrow();

  expect(count).toBe(1);
});

错误原因

  1. vi.mock的提升机制:Vitest会把vi.mock提升到模块最顶部执行,此时测试用例内部定义的变量(比如mocks)还未初始化,工厂函数引用这些变量就会触发错误。
  2. Mock未覆盖目标导出:原database.ts导出的是默认的db实例,但测试代码里Mock的是ownerDb,完全没覆盖目标对象。
  3. 未Mock查询方法:即使创建了Mock的Kysely实例,也没有Mock查询链的关键方法,测试时还是会尝试执行真实数据库逻辑。

解决方案

调整后的测试代码

import { it, vi, expect } from "vitest";
import db from "../database/database";
import { DB } from "../types/schema.types";
import { Kysely } from "kysely";

// 用vi.hoisted定义Mock变量,这些变量会被同步提升到模块顶部,和vi.mock同层级
const mocks = vi.hoisted(() => ({
  executeTakeFirstOrThrow: vi.fn().mockResolvedValue({ count: 1 }),
  selectNoFrom: vi.fn().mockReturnThis(),
  select: vi.fn().mockReturnThis(),
  selectFrom: vi.fn().mockReturnThis(),
}));

// 将vi.mock移到模块顶层,避免引用测试用例内的局部变量
vi.mock("../database/database", async () => {
  return {
    __esModule: true,
    // 正确覆盖默认导出的db实例,Mock查询链的核心方法
    default: {
      selectFrom: mocks.selectFrom,
      select: mocks.select,
      selectNoFrom: mocks.selectNoFrom,
      executeTakeFirstOrThrow: mocks.executeTakeFirstOrThrow,
    } as unknown as Kysely<DB>,
  };
});

it("Get user count", async () => {
  const query = db
    .selectFrom("users")
    .select((eb) => eb.fn.count<number>("users.user_id").as("count"));

  const { count } = await db
    .selectNoFrom((eb) => eb.fn.coalesce(query, eb.lit(0)).as("count"))
    .executeTakeFirstOrThrow();

  expect(count).toBe(1);
  // 验证查询方法是否按预期被调用
  expect(mocks.selectFrom).toHaveBeenCalledWith("users");
});

优化建议

  • 使用Kysely官方MockDialect:如果需要兼容更复杂的查询构建逻辑,推荐使用Kysely提供的MockDialect,它能自动处理查询链的Mock,无需手动Mock每个方法。
  • 隔离环境变量:测试时可以Mockdotenv模块,避免加载真实的.env配置,防止测试环境受生产配置影响。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 05:44:53