带有数据库依赖的TypeScript Lambda Handler测试方案咨询
解决TypeScript Lambda Handler测试时无法Mock数据库的方案
你的核心问题在于Lambda Handler内部硬编码了myService和myRepository的实例化逻辑,导致测试时无法替换为Mock对象。以下两种成熟方案可以解决这个问题,无需依赖本地Docker或内存数据库:
方案一:重构Handler支持依赖注入(推荐)
通过打破Handler内部的硬编码依赖,让外部可以传入Mock实例,同时保持生产环境的正常逻辑。
方式1:给Handler添加可选依赖参数
修改Handler代码,将依赖作为可选参数传入,默认使用真实实现:
import { APIGatewayProxyCallback } from "aws-lambda"; import { myService } from "./service/myService"; import { myRepository } from "./repository/myRepository"; // 定义依赖类型,确保类型安全 type HandlerDependencies = { service?: myService; }; export const lambdaHandler = async ( event: any, context: any, callback: APIGatewayProxyCallback, deps: HandlerDependencies = {}, ): Promise<void> => { // 优先使用传入的依赖,否则创建真实实例 const service = deps.service || new myService(new myRepository()); // 注意:原代码漏了await,异步方法需等待执行结果 const res = await service.execute(event); callback(null, { statusCode: 200, body: JSON.stringify(res), }); };
测试时直接传入Mock的Service:
import { lambdaHandler } from "./handler"; import { myService } from "./service/myService"; // 创建Mock Service实例 const mockService = { execute: jest.fn().mockResolvedValue({ data: "模拟返回数据" }) } as unknown as myService; test("Lambda Handler返回模拟数据", async () => { const mockCallback = jest.fn(); await lambdaHandler({}, {}, mockCallback, { service: mockService }); // 验证回调被正确调用 expect(mockCallback).toHaveBeenCalledWith(null, { statusCode: 200, body: JSON.stringify({ data: "模拟返回数据" }) }); // 验证Service的execute方法被调用 expect(mockService.execute).toHaveBeenCalledWith({}); });
方式2:用依赖工厂函数解耦
创建单独的依赖工厂模块,Handler通过工厂获取依赖,测试时Mock工厂函数:
- 创建
deps.ts:
import { myService } from "./service/myService"; import { myRepository } from "./repository/myRepository"; export const getService = () => new myService(new myRepository());
- 修改Handler:
import { APIGatewayProxyCallback } from "aws-lambda"; import { getService } from "./deps"; export const lambdaHandler = async ( event: any, context: any, callback: APIGatewayProxyCallback, ): Promise<void> => { const service = getService(); const res = await service.execute(event); callback(null, { statusCode: 200, body: JSON.stringify(res), }); };
- 测试代码:
import { lambdaHandler } from "./handler"; import * as deps from "./deps"; import { myService } from "./service/myService"; test("Lambda Handler使用模拟Service", async () => { const mockService = { execute: jest.fn().mockResolvedValue({ result: "测试数据" }) } as unknown as myService; // Mock工厂函数,返回模拟Service jest.spyOn(deps, "getService").mockReturnValue(mockService); const mockCallback = jest.fn(); await lambdaHandler({}, {}, mockCallback); expect(deps.getService).toHaveBeenCalled(); expect(mockService.execute).toHaveBeenCalled(); expect(mockCallback).toHaveBeenCalledWith(null, { statusCode: 200, body: JSON.stringify({ result: "测试数据" }) }); });
方案二:直接Mock依赖模块(无需大改业务代码)
如果不想重构Handler,可使用测试框架的模块Mock功能(比如Jest的jest.mock),直接替换myRepository或myService模块的实现,让Handler内部实例化时得到Mock对象。
Mock myService模块
import { lambdaHandler } from "./handler"; import { myService } from "./service/myService"; // Mock整个myService模块 jest.mock("./service/myService", () => { return { myService: jest.fn().mockImplementation(() => { return { execute: jest.fn().mockResolvedValue({ data: "模拟结果" }) }; }) }; }); test("Lambda Handler调用模拟Service", async () => { const mockCallback = jest.fn(); await lambdaHandler({}, {}, mockCallback); // 验证myService被实例化 expect(myService).toHaveBeenCalled(); // 获取模拟实例,验证方法调用 const mockServiceInstance = myService.mock.instances[0]; expect(mockServiceInstance.execute).toHaveBeenCalledWith({}); // 验证返回结果 expect(mockCallback).toHaveBeenCalledWith(null, { statusCode: 200, body: JSON.stringify({ data: "模拟结果" }) }); });
Mock myRepository模块
import { lambdaHandler } from "./handler"; import { myRepository } from "./repository/myRepository"; // Mock整个myRepository模块 jest.mock("./repository/myRepository", () => { return { myRepository: jest.fn().mockImplementation(() => { // 模拟Repository的核心方法 return { query: jest.fn().mockResolvedValue([{ id: 1, name: "模拟数据" }]) }; }) }; }); test("Lambda Handler调用模拟Repository", async () => { const mockCallback = jest.fn(); await lambdaHandler({}, {}, mockCallback); // 验证Repository被实例化 expect(myRepository).toHaveBeenCalled(); // 获取模拟实例,验证方法调用 const mockRepoInstance = myRepository.mock.instances[0]; expect(mockRepoInstance.query).toHaveBeenCalled(); });
方案对比
- 方案一:符合依赖注入设计原则,代码可测试性、扩展性更强,是长期维护的最佳选择。
- 方案二:无需修改业务代码,适合快速编写测试,但模块Mock可能会导致测试与模块耦合度较高。
内容的提问来源于stack exchange,提问作者Sheen
相关产品推荐
相关产品推荐

