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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 23:05:17