如何为同一接口的多个实现复用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
相关产品推荐
相关产品推荐

