0

I am trying to test my command handler which has multiple repositories injected into it. When running the test I am getting the following error:

ValidationError: Using global EntityManager instance methods for context specific actions is disallowed. If you need to work with the global instance's identity map, use `allowGlobalContext` configuration option or `fork()` instead.

My DatabaseModule boots up the MikroORM connection.

I understand that with the app, RequestContext is created with each request which provides me with a fresh instance of EntityManager and it is working perfectly fine. But for testing I cannot figure out how to do it.

This is the command handler itself (removed the actual handle function)

@Injectable()
export class RegisterUserCommandHandler
  implements ICommandHandler<RegisterUserCommand, { userId: string }>
{
  private readonly logger = new Logger(RegisterUserCommandHandler.name);

  constructor(
    private readonly authorizationService: AuthorizationService,
    private readonly userFactory: UserFactory,
    private readonly userRepository: UserRepository,
    private readonly physicianRepository: PhysicianRepository,
    private readonly representativeRepository: LocalRepresentativeRepository,
    private readonly pharmacistRepository: PharmacistRepository
  ) {}
}

This is the test code (I only pasted the relevant section)

import { Test } from "@nestjs/testing";
import { ConfigModule } from "@nestjs/config";
import { EventEmitterModule } from "@nestjs/event-emitter";
import { MikroORM, EntityManager } from "@mikro-orm/core";
import { isLeft, isRight } from "fp-ts/Either";

describe("RegisterUserCommandHandler", () => {
  let orm: MikroORM;
  let entityManager: EntityManager;
  let handler: RegisterUserCommandHandler;
  let userFactory: UserFactory;
  let userRepository: UserRepository;
  let physicianRepository: PhysicianRepository;
  let representativeRepository: LocalRepresentativeRepository;
  let pharmacistRepository: PharmacistRepository;

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [
        EventEmitterModule.forRoot(),
        ConfigModule.forRoot({
          isGlobal: true,
        }),
        DatabaseModule,
      ],
      providers: [
        RegisterUserCommandHandler,
        {
          provide: AuthorizationService,
          useValue: {
            isUserAuthorizedAsAdmin: jest.fn(),
          },
        },
        {
          provide: PasswordGenerator,
          useValue: {
            generatePasswordHash: jest.fn(),
            validatePassword: jest.fn(),
            generateRandomPassword: jest.fn().mockResolvedValue("Password1"),
          },
        },
        UserFactory,
        UserRepository,
        PhysicianRepository,
        LocalRepresentativeRepository,
        PharmacistRepository,
      ],
    }).compile();

    orm = moduleRef.get<MikroORM>(MikroORM);
    handler = moduleRef.get<RegisterUserCommandHandler>(
      RegisterUserCommandHandler
    );
    userFactory = moduleRef.get<UserFactory>(UserFactory);
    userRepository = moduleRef.get<UserRepository>(UserRepository);
    physicianRepository =
      moduleRef.get<PhysicianRepository>(PhysicianRepository);
    representativeRepository = moduleRef.get<LocalRepresentativeRepository>(
      LocalRepresentativeRepository
    );
    pharmacistRepository =
      moduleRef.get<PharmacistRepository>(PharmacistRepository);

    
    entityManager = orm.em.fork();
  });
});

This is the base repository (every repository extends this in the system)

import {
  EntityManager,
  EntityRepository,
  SqlEntityManager,
} from "@mikro-orm/mysql";
import { AnyEntity, EntityName } from "@mikro-orm/core";
import { FindOptions } from "@mikro-orm/core/drivers/IDatabaseDriver";

import { IRepository } from "./repository.interface";
import { SOFT_DELETABLE_FILTER } from "../decorators";
import type { PopulatePath } from "@mikro-orm/core/enums";

export abstract class BaseRepository<T extends AnyEntity<T>>
  extends EntityRepository<T>
  implements IRepository<T>
{
  protected constructor(em: SqlEntityManager, entity: EntityName<T>) {
    super(em, entity);
  }

  //...more code here
}

And finally this is the database module

import { Module } from "@nestjs/common";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { MikroOrmModule } from "@mikro-orm/nestjs";

import { CoreModule, SoftDeletableHandlerSubscriber } from "@riskomed/core";

import { DatabaseConfig } from "./database.config";
import { GlobalSubscriber } from "./global.subscriber";
import mikroOrmConfig from "./mikro-orm.config";

@Module({
  imports: [
    CoreModule,
    ConfigModule.forFeature(DatabaseConfig),
    MikroOrmModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (configService: ConfigService) => ({
        ...mikroOrmConfig(configService),
      }),
    }),
  ],
  providers: [GlobalSubscriber, SoftDeletableHandlerSubscriber],
})
export class DatabaseModule {}

How can I get past that error without using the allowGlobalContext?

1 Answer 1

0

Here's how you can do it:

Test Configuration

First, update your test setup to include a custom provider for EntityManager:

import { Test } from "@nestjs/testing";
import { ConfigModule } from "@nestjs/config";
import { EventEmitterModule } from "@nestjs/event-emitter";
import { MikroORM, EntityManager } from "@mikro-orm/core";
import { getRepositoryToken } from "@mikro-orm/nestjs"; // Ensure this import
import { RegisterUserCommandHandler } from './path-to-handler'; // Ensure correct path
import { DatabaseModule } from './path-to-database-module'; // Ensure correct path
import { UserFactory } from './path-to-user-factory'; // Ensure correct path
import { UserRepository } from './path-to-user-repository'; // Ensure correct path
import { PhysicianRepository } from './path-to-physician-repository'; // Ensure correct path
import { LocalRepresentativeRepository } from './path-to-local-representative-repository'; // Ensure correct path
import { PharmacistRepository } from './path-to-pharmacist-repository'; // Ensure correct path
import { AuthorizationService } from './path-to-authorization-service'; // Ensure correct path
import { PasswordGenerator } from './path-to-password-generator'; // Ensure correct path

describe("RegisterUserCommandHandler", () => {
let orm: MikroORM;
let handler: RegisterUserCommandHandler;
let entityManager: EntityManager;

beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
    imports: [
        EventEmitterModule.forRoot(),
        ConfigModule.forRoot({ isGlobal: true }),
        DatabaseModule,
    ],
    providers: [
        RegisterUserCommandHandler,
        {
        provide: AuthorizationService,
        useValue: {
            isUserAuthorizedAsAdmin: jest.fn(),
        },
        },
        {
        provide: PasswordGenerator,
        useValue: {
            generatePasswordHash: jest.fn(),
            validatePassword: jest.fn(),
            generateRandomPassword: jest.fn().mockResolvedValue("Password1"),
        },
        },
        UserFactory,
        UserRepository,
        PhysicianRepository,
        LocalRepresentativeRepository,
        PharmacistRepository,
        {
        provide: EntityManager,
        useFactory: (orm: MikroORM) => orm.em.fork(),
        inject: [MikroORM],
        },
        {
        provide: getRepositoryToken(UserRepository),
        useFactory: (em: EntityManager) => em.getRepository(UserRepository),
        inject: [EntityManager],
        },
        {
        provide: getRepositoryToken(PhysicianRepository),
        useFactory: (em: EntityManager) => em.getRepository(PhysicianRepository),
        inject: [EntityManager],
        },
        {
        provide: getRepositoryToken(LocalRepresentativeRepository),
        useFactory: (em: EntityManager) => em.getRepository(LocalRepresentativeRepository),
        inject: [EntityManager],
        },
        {
        provide: getRepositoryToken(PharmacistRepository),
        useFactory: (em: EntityManager) => em.getRepository(PharmacistRepository),
        inject: [EntityManager],
        },
    ],
    }).compile();

    orm = moduleRef.get<MikroORM>(MikroORM);
    handler = moduleRef.get<RegisterUserCommandHandler>(RegisterUserCommandHandler);
    entityManager = moduleRef.get<EntityManager>(EntityManager);
});

// Add your tests here
});

Explanation

  1. Custom EntityManager Provider: We provide a custom EntityManager that calls orm.em.fork() to ensure each test uses a fresh EntityManager instance.
  2. Custom Repository Providers: We create custom providers for each repository to inject the forked EntityManager into them.
  3. Module Imports and Providers: Ensure all required modules and providers are correctly imported and configured in your test module.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.