NestJS Jest测试中如何Mock TypeORM的DataSource?
NestJS自定义Repository的Jest测试问题解决
问题描述
测试NestJS项目时,自定义SessionRepository因依赖DataSource无法在测试模块中解析,报错信息如下:
Nest can't resolve dependencies of the SessionRepository (?). Please make sure that the argument DataSource at index [0] is available in the RootTestModule context. Potential solutions: - If DataSource is a provider, is it part of the current RootTestModule? - If DataSource is exported from a separate @Module, is that module imported within RootTestModule? @Module({ imports: [ /* the Module containing DataSource */ ] })
业务代码
dummy.controller.ts
@Controller(UrlConstants.BASE_URL + 'dummy') export class DummyContoller { constructor( private readonly sessionService: SessionService, ) { } @Get('validateSession') async checkValidateSession(@Query('sessionId') sessionId: string) { const session = await this.sessionService.validateSession(sessionId); console.log(session); return { message: "OK" }; } }
session.service.ts
@Injectable() export class SessionService { constructor( private readonly sessionRepo: SessionRepository, private readonly accountRepo: AccountRepository ) { } @WithErrorBoundary(AuthCodes.UNKNOWN_LOGIN_ERROR) async validateSession(sessionId: string) { const session = await this.sessionRepo.findOneBy({ sessionId }); if (!session || this.isSessionExpired(session)) { session && await this.sessionRepo.remove(session); throw new HttpException({ code: AuthCodes.SESSION_TIMEOUT, message: AuthMessages.SESSION_TIMEOUT }, HttpStatus.UNAUTHORIZED) } return session; } ... }
session.repository.ts(自定义Repository)
@Injectable() export class SessionRepository extends Repository<Session> { constructor(private dataSource: DataSource) { super(Session, dataSource.createEntityManager()) } ... }
原测试代码
describe('DummyController', () => { let dummyContoller: DummyContoller; let sessionService: SessionService; let sessionRepo: SessionRepository; let accountRepo: AccountRepository; beforeEach(async () => { const module = await Test.createTestingModule({ controllers: [DummyContoller], providers: [SessionService, SessionRepository, AccountRepository] }).compile(); dummyContoller = module.get<DummyContoller>(DummyContoller); sessionService = module.get<SessionService>(SessionService); sessionRepo = module.get<SessionRepository>(SessionRepository); accountRepo = module.get<AccountRepository>(AccountRepository); }) describe('checkValidateSession', () => { it('should return valid session', async () => { const sessionId = "sessionId1"; const session = new Session(); jest.spyOn(sessionService, 'validateSession').mockImplementation(async (sessionId) => session); expect(await dummyContoller.checkValidateSession(sessionId)).toBe(session); }) }) })
解决方案
报错核心原因是测试模块直接引入了真实的SessionRepository,但未提供其依赖的DataSource。针对不同测试层级,提供以下解决方案:
方案1:Controller层测试(Mock Service依赖)
Controller测试只需验证与Service的交互逻辑,无需初始化真实的Repository和DataSource,直接MockSessionService即可:
describe('DummyController', () => { let dummyContoller: DummyContoller; let sessionService: jest.Mocked<SessionService>; beforeEach(async () => { // 创建Mock的SessionService const mockSessionService = { validateSession: jest.fn() } as jest.Mocked<SessionService>; const module = await Test.createTestingModule({ controllers: [DummyContoller], providers: [ { provide: SessionService, useValue: mockSessionService } ] }).compile(); dummyContoller = module.get<DummyContoller>(DummyContoller); sessionService = module.get(SessionService); }) describe('checkValidateSession', () => { it('should return { message: "OK" } when session is valid', async () => { const sessionId = "sessionId1"; const mockSession = new Session(); // 配置Mock方法返回有效会话 sessionService.validateSession.mockResolvedValue(mockSession); const result = await dummyContoller.checkValidateSession(sessionId); // 注意Controller实际返回固定格式响应,不是session对象 expect(result).toEqual({ message: "OK" }); // 验证Service方法被正确调用 expect(sessionService.validateSession).toHaveBeenCalledWith(sessionId); }) it('should throw HttpException when session is invalid', async () => { const sessionId = "invalidSession"; // 配置Mock方法抛出会话超时异常 sessionService.validateSession.mockRejectedValue(new HttpException({ code: AuthCodes.SESSION_TIMEOUT, message: AuthMessages.SESSION_TIMEOUT }, HttpStatus.UNAUTHORIZED)); // 断言Controller会抛出对应异常 await expect(dummyContoller.checkValidateSession(sessionId)).rejects.toThrow(HttpException); }) }) })
方案2:Service层测试(Mock自定义Repository)
如果需要测试SessionService的业务逻辑,只需Mock自定义Repository的核心方法,无需依赖真实的DataSource:
describe('SessionService', () => { let sessionService: SessionService; let sessionRepo: jest.Mocked<SessionRepository>; beforeEach(async () => { // 创建Mock的SessionRepository,模拟所需方法 const mockSessionRepo = { findOneBy: jest.fn(), remove: jest.fn() } as jest.Mocked<SessionRepository>; const mockAccountRepo = {} as AccountRepository; const module = await Test.createTestingModule({ providers: [ SessionService, { provide: SessionRepository, useValue: mockSessionRepo }, { provide: AccountRepository, useValue: mockAccountRepo } ] }).compile(); sessionService = module.get<SessionService>(SessionService); sessionRepo = module.get(SessionRepository); }) it('should return valid session', async () => { const sessionId = "validSession"; const mockSession = new Session(); mockSession.expiresAt = new Date(Date.now() + 3600000); // 设置为未过期 sessionRepo.findOneBy.mockResolvedValue(mockSession); const result = await sessionService.validateSession(sessionId); expect(result).toBe(mockSession); expect(sessionRepo.findOneBy).toHaveBeenCalledWith({ sessionId }); }) it('should throw SESSION_TIMEOUT when session is expired', async () => { const sessionId = "expiredSession"; const mockSession = new Session(); mockSession.expiresAt = new Date(Date.now() - 3600000); // 设置为已过期 sessionRepo.findOneBy.mockResolvedValue(mockSession); await expect(sessionService.validateSession(sessionId)).rejects.toThrow(HttpException); expect(sessionRepo.remove).toHaveBeenCalledWith(mockSession); }) })
方案3:临时Mock DataSource(不推荐,仅特殊场景使用)
如果必须在测试中使用真实的Repository(会增加测试复杂度),可以提供一个Mock的DataSource:
beforeEach(async () => { const mockDataSource = {} as DataSource; // 补充mock createEntityManager方法 mockDataSource.createEntityManager = jest.fn(() => ({}) as EntityManager); const module = await Test.createTestingModule({ controllers: [DummyContoller], providers: [ SessionService, SessionRepository, AccountRepository, { provide: DataSource, useValue: mockDataSource } ] }).compile(); // ... 后续实例获取代码 })
内容的提问来源于stack exchange,提问作者veeru153
相关产品推荐
相关产品推荐

