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

如何为同一接口的多个实现复用Jest测试用例?

复用ConfigRepository测试用例适配不同实现的方案

现有测试用例与接口

针对内存实现的测试用例

describe("Given a config repository", () => {
    let target: ConfigRepository;

    beforeEach(() => {
        target = InMemoryConfigRepository();
    });

    test("When creating a new config, Then it is persisted properly", async () => {
        const key = "key";
        const namespace = "namespace";
        const result = await target.upsert({
            key,
            namespace,
            value: "value",
            teamId: "teamId",
        })();
        const configs = await target.findAll()();

        expect(result._tag).toEqual("Right");
        expect(configs.length).toBe(1);
        expect(configs.map((c) => c.key)).toEqual([key]);
    });
});

ConfigRepository接口定义

export type ConfigRepository = {
    get: <T extends RequiredJsonObject>(
        props: ConfigurationProperties<T>
    ) => TE.TaskEither<DatabaseError | MissingConfigError, ConfigEntity[]>;
    findAll(): T.Task<ConfigEntity[]>;
    upsert: (
        config: UnsavedConfig
    ) => TE.TaskEither<DatabaseError, ConfigEntity>;
};

复用测试用例的实现方案

核心思路是抽离通用测试逻辑,针对不同的Repository实现复用,以下是两种实用方案(以Jest为例):

方式一:抽离通用测试逻辑为函数

把测试用例的核心断言逻辑封装成独立函数,针对每个Repository实现单独创建测试块,初始化对应实例即可。

// 抽离通用测试函数
const runConfigRepositoryTests = (getRepository: () => ConfigRepository) => {
    let target: ConfigRepository;

    beforeEach(() => {
        target = getRepository();
    });

    test("When creating a new config, Then it is persisted properly", async () => {
        const key = "key";
        const namespace = "namespace";
        const result = await target.upsert({
            key,
            namespace,
            value: "value",
            teamId: "teamId",
        })();
        const configs = await target.findAll()();

        expect(result._tag).toEqual("Right");
        expect(configs.length).toBe(1);
        expect(configs.map((c) => c.key)).toEqual([key]);
    });

    // 可在此添加更多通用测试用例
};

// 测试内存实现
describe("Given an InMemoryConfigRepository", () => {
    runConfigRepositoryTests(() => InMemoryConfigRepository());
});

// 测试Prisma实现
describe("Given a PrismaConfigRepository", () => {
    // 针对Prisma添加数据库初始化/清理逻辑
    beforeEach(async () => {
        await prisma.config.deleteMany({});
    });

    runConfigRepositoryTests(() => PrismaConfigRepository(prisma));
});

方式二:使用参数化测试批量生成

通过describe.each批量传入不同的Repository实现及其描述,自动生成对应测试块,适合多实现场景。

// 定义要测试的实现列表
const repositoryImplementations = [
    ["InMemoryConfigRepository", () => InMemoryConfigRepository()],
    ["PrismaConfigRepository", () => PrismaConfigRepository(prisma)],
];

// 批量生成测试
describe.each(repositoryImplementations)("Given a %s", (name, getRepository) => {
    let target: ConfigRepository;

    beforeEach(() => {
        target = getRepository();
    });

    // 针对Prisma添加特殊清理逻辑
    if (name === "PrismaConfigRepository") {
        beforeEach(async () => {
            await prisma.config.deleteMany({});
        });
    }

    test("When creating a new config, Then it is persisted properly", async () => {
        const key = "key";
        const namespace = "namespace";
        const result = await target.upsert({
            key,
            namespace,
            value: "value",
            teamId: "teamId",
        })();
        const configs = await target.findAll()();

        expect(result._tag).toEqual("Right");
        expect(configs.length).toBe(1);
        expect(configs.map((c) => c.key)).toEqual([key]);
    });
});

注意事项

  • 对于有外部依赖的实现(如Prisma),必须在beforeEach/afterEach中添加初始化和清理逻辑,避免测试用例之间的污染。
  • 若某个实现需要特殊测试用例,可在对应的describe块中单独添加,不影响通用逻辑的复用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 15:15:29