How to properly mock TypeORMModule in NestJs and Jest

Viewed 33

I have build a BaseTest from which all my other tests inherit. I am trying to mock the database connection and the database objects within this test. But the test fails to run when I don't have the database up, which tells me it's not mocking it properly.

Here is my BaseTest file:

import * as supertest from 'supertest';
import { ExecutionContext, INestApplication, Type, ValidationPipe } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { AppModule } from '../app.module';
import { AuthGuard } from '@nestjs/passport';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Account } from '../accounts/entities/account.entity';
import { Session } from '../accounts/entities/session.entity';
import { Repository } from 'typeorm';
import { ApiKey } from '../accounts/entities/apikey.entity';
import { Project } from '../accounts/entities/project.entity';
import { Operator } from '../accounts/entities/operator.entity';
import { ProjectRole } from '../accounts/entities/project-roles.entity';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { GlobalGuard } from '../auth/global.guard';
import { ProjectGuard } from '../auth/project.guard';
import { ModuleRef } from '@nestjs/core';

type MockRepository<T = any> = Partial<Record<keyof Repository<T>, jest.Mock>>;
const createMockRepository = <T = any>(): MockRepository<T> => ({
    findOne: jest.fn(),
    create: jest.fn(),
    save: jest.fn(),
    find: jest.fn(),
    preload: jest.fn(),
    remove: jest.fn(),
});

export abstract class BaseTest {
    static app: INestApplication;
    static httpServer: any;
    static accountRepository: MockRepository;
    static projectRepository: MockRepository;
    static apikeyRepository: MockRepository;
    static operatorRepository: MockRepository;
    static projectRoleRepository: MockRepository;
    static sessionRepository: MockRepository;
    static fakeRequest: any;
    static fakeApiRequest: any;

    // runs before all tests
    static async before(): Promise<void> {
        const moduleRef = await Test.createTestingModule({
            imports: [AppModule],
        })
            .overrideGuard(JwtAuthGuard)
            .useValue({
                canActivate: (ctx: ExecutionContext) => {
                    const req = ctx.switchToHttp().getRequest();
                    req.user = { operatorId: 'opid', accountId: '123', email: 'op@gmail.com' };
                    return true;
                },
            })
            .overrideGuard(GlobalGuard)
            .useValue({
                canActivate: (ctx: ExecutionContext) => {
                    return true;
                },
            })
            .overrideGuard(ProjectGuard)
            .useValue({
                canActivate: (ctx: ExecutionContext) => {
                    return true;
                },
            })
            .overrideGuard(AuthGuard('api-key'))
            .useValue({
                canActivate: (ctx: ExecutionContext) => {
                    const req = ctx.switchToHttp().getRequest();

                    req['account'] = { id: '123', name: 'test', slug: 'test' };
                    return true;
                },
            })
            .overrideProvider(getRepositoryToken(Account))
            .useValue(createMockRepository())
            .overrideProvider(getRepositoryToken(Project))
            .useValue(createMockRepository())
            .overrideProvider(getRepositoryToken(ApiKey))
            .useValue(createMockRepository())
            .overrideProvider(getRepositoryToken(Operator))
            .useValue(createMockRepository())
            .overrideProvider(getRepositoryToken(ProjectRole))
            .useValue(createMockRepository())
            .overrideProvider(getRepositoryToken(Session))
            .useValue(createMockRepository())

            .overrideProvider('database')
            .useValue({
                insert: jest.fn().mockReturnValue({
                    toPromise: jest.fn().mockResolvedValue(true),
                }),
                query: jest.fn().mockReturnValue({
                    toPromise: jest.fn().mockResolvedValue(true),
                }),
                queryPromise: jest.fn(),
            })
            .compile();

        BaseTest.app = moduleRef.createNestApplication();
        BaseTest.app.useGlobalPipes(new ValidationPipe({ transform: true }));
        BaseTest.app = await BaseTest.app.init();
        BaseTest.httpServer = this.app.getHttpServer();

        BaseTest.accountRepository = moduleRef.get<MockRepository>(getRepositoryToken(Account));
        BaseTest.projectRepository = moduleRef.get<MockRepository>(getRepositoryToken(Project));
        BaseTest.apikeyRepository = moduleRef.get<MockRepository>(getRepositoryToken(ApiKey));
        BaseTest.operatorRepository = moduleRef.get<MockRepository>(getRepositoryToken(Operator));
        BaseTest.projectRoleRepository = moduleRef.get<MockRepository>(getRepositoryToken(ProjectRole));
        BaseTest.sessionRepository = moduleRef.get<MockRepository>(getRepositoryToken(Session));

        BaseTest.fakeRequest = { user: { sessionId: 'aaa', operatorId: 'opid', email: 'op@gmail.com', accountId: '123' } };
        BaseTest.fakeApiRequest = { projectId: 'projectid' };
    }

    static async after(): Promise<void> {
        expect.hasAssertions();
        BaseTest.app = null;
    }

    get<TInput = any, TResult = TInput>(type: Type<TInput> | string | symbol): TResult {
        return BaseTest.app.get(type);
    }

    server(): supertest.SuperTest<supertest.Test> {
        return supertest(BaseTest.httpServer);
    }
}

And here is my App.module file:

import { Global, Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.module';
import { GroupsModule } from './groups/groups.module';
import { EventsModule } from './events/events.module';
import { AccountsModule } from './accounts/accounts.module';
import { AuthModule } from './auth/auth.module';
import { ConfigModule } from '@nestjs/config';
import { TestdbModule } from './testdb/testdb.module';
import { BullModule } from '@nestjs/bull';
import { ClickHouseModule } from '@depyronick/nestjs-clickhouse';
import { TypeOrmModule } from '@nestjs/typeorm';
import { MailModule } from './mail/mail.module';
import { IntegrationsModule } from './integrations/integrations.module';

@Global()
@Module({
    imports: [
        UsersModule,
        TypeOrmModule.forRoot({
            type: 'postgres',
            host: process.env.POSTGRES_DATABASE_HOST,
            port: parseInt(process.env.POSTGRES_DATABASE_PORT),
            username: process.env.POSTGRES_DATABASE_USERNAME,
            password: process.env.POSTGRES_DATABASE_PASSWORD,
            database: process.env.POSTGRES_DATABASE_NAME,
            logging: false,
            autoLoadEntities: true,
            synchronize: false,
        }),
        GroupsModule,
        EventsModule,
        AccountsModule,
        ClickHouseModule.register([
            {
                name: 'database',
                host: process.env.CLICKHOUSE_DATABASE_HOST,
                database: process.env.CLICKHOUSE_DATABASE_NAME,
                username: process.env.CLICKHOUSE_DATABASE_USERNAME,
                password: process.env.CLICKHOUSE_DATABASE_PASSWORD,
            },
        ]),
        ConfigModule.forRoot({ isGlobal: true }),
        BullModule.forRoot({
            redis: {
                host: process.env.REDIS_HOST,
                port: parseInt(process.env.REDIS_PORT),
                password: process.env.REDIS_PASSWORD,
            },
            defaultJobOptions: { removeOnComplete: true },
        }),
        AuthModule,
        TestdbModule,
        IntegrationsModule,
        MailModule,
    ],
    controllers: [AppController],
    providers: [AppService],
})
export class AppModule {}

My guess is that when the basetest imports the app.module, it tries to import the TypeormModule from there and ends up trying to connect to the database. How could I avoid that from happening?

0 Answers
Related