使用AsyncWrapper的repoWrap包装后测试报错及返回undefined问题排查
问题解答
问题1:仓库方法包装后返回undefined的原因
核心问题是**repoWrap方法中fn.apply(this)的this指向错误**:
- 在
UserRepository中,findUserById通过this.wrap.repoWrap包装,repoWrap内部调用fn.apply(this)时,此处的this指向AsyncWrapper实例,而非UserRepository实例。 - 原异步函数依赖
UserRepository上下文的this.database,指向错误导致this.database为undefined,数据库查询无法执行,最终返回undefined。
同时测试环节的自动mock也有影响:
vi.mock默认仅mock类的原型方法,而findUserById是类的实例属性(箭头函数),自动mock不会为其生成mock实现,调用时自然返回undefined。
修复方案
修改repoWrap,让被包装函数继承原调用上下文:
repoWrap = (fn: Function) => { return function(...args: any) { // 改用普通函数保留调用时的this return fn.apply(this, args).catch((error: any) => { throw DatabaseException.error(error); }); }; };
同时在测试中手动mock实例属性:
beforeEach(() => { // 手动将findUserById设为spy并模拟返回值 UserRepository.prototype.findUserById = vi.fn().mockResolvedValue({ /* 模拟用户数据 */ }); userRepository = new UserRepository(); userService = new UserService(userRepository); });
问题2:toHaveBeenCalled抛出类型错误的原因
findUserById是UserRepository的实例属性箭头函数,而非原型方法。vi.mock自动mock类时,仅会将原型上的方法转为spy,实例属性不会被自动处理,因此userRepository.findUserById不是spy对象,调用toHaveBeenCalled会抛出undefined is not a spy的错误。
修复方案
方案1:手动mock实例属性为spy
beforeEach(() => { const mockFindUserById = vi.fn(); // 替换类的实例属性为spy UserRepository.prototype.findUserById = mockFindUserById; userRepository = new UserRepository(); userService = new UserService(userRepository); });
方案2:将仓库方法改为原型方法
class UserRepository implements IUserRepository { private database: Kysely<DB>; private wrap: AsyncWrapper = new AsyncWrapper(); constructor() { this.database = db; // 在构造函数中绑定上下文并包装 this.findUserById = this.wrap.repoWrap(this.findUserById.bind(this)); }; // 改为原型方法 public async findUserById(user_id: number): Promise<SelectUsers | undefined> { return await this.database .selectFrom("users") .where("user_id", "=", user_id) .selectAll() .executeTakeFirst(); } };
修改后vi.mock会自动将其转为spy,测试时可正常调用toHaveBeenCalled断言。
内容的提问来源于stack exchange,提问作者Almighty Dee
相关产品推荐
相关产品推荐

