NestJS Test EntityManager transactions

  Kiến thức lập trình

I don’t have a lot of experience with working TypeORM through jest/vitest so this may just be a misunderstanding of how to go about it.

Basically, I have an EntityManager in one of my classes and a call to its transaction function to ensure several inserts across multiple tables all succeed before committing.

@Injectable()
export class InvoiceService implements OnApplicationBootstrap {
constructor(
    @InjectEntityManager()
    private dbManager: EntityManager,
    @InjectRepository(Invoice)
    private invoiceRepo: Repository<Invoice> 
...
) 

async savePayloadToDB(payload: InvoiceDto, originalJSON: Record<any, any>) { 
...
    const rowResponse = await this.dbManager.transaction(async (transactionManager) => {
        *call to several private functions including insert using
        *   await transactionManager
        *   .createQueryBuilder()
        *   .insert()
        *   .into(...)
        *   .values({...})
        *   .execute()
        //nothing in here is actually checked if I mock the entire transaction call
}

I don’t want to mock the actual transaction call, just the calls inside of it. The problem is, if I mock the EntityManager, then it’s obviously not initialized and when the tests try to call transaction, you get the classic undefined problem

const createMockQueryBuilder: any = {
        insert: () => createMockQueryBuilder,
        into: () => createMockQueryBuilder,
        values: () => createMockQueryBuilder,
        execute: () => { throw new Error('duplicate key value violates unique constraint "benefit_id"') }
    }
    jest.spyOn(mockDbManager, 'createQueryBuilder').mockImplementation(() => createMockQueryBuilder)

    expect.assertions(1)
    await expect(invoiceService.processInvoice('test', validPayload, {})).rejects.toEqual(Error('duplicate key value violates unique constraint "benefit_id"'))
...
TypeError: Cannot read properties of undefined (reading 'createQueryRunner')

For the record, I have the entity manager mocked via the getEntityManagerToken provided by typeorm for testing

beforeEach(async () => {
    const app: TestingModule = await Test.createTestingModule({
        imports: [HttpModule],
        providers: [InvoiceService, StatusAndOutboxService, OrderPaymentService, UtilityFunctions,
            {
                provide: getRepositoryToken(Invoice),
                useClass: Repository
            },
            {
                provide: getEntityManagerToken(),
                useClass: EntityManager
            }
        ]
    }).compile()

    invoiceService = app.get<InvoiceService>(InvoiceService)
    mockInvoiceRepository = app.get<Repository<Invoice>>(getRepositoryToken(Invoice))
    mockDbManager = app.get<EntityManager>(getEntityManagerToken())
    ...

I think what’s happening is that the EntityManager’s transaction call, under the hood, is basically just chaining the normal QueryRunner startTransaction -> commitTransaction/rollbackTransaction calls you can manually do anyways (https://orkhan.gitbook.io/typeorm/docs/transactions)

So obviously, I don’t have a QueryRunner because EntityManager is being mocked. I could mock the createQueryRunner call, but that’s not actually called directly by EntityManager. I think the chain is Datasource -> QueryRunner -> EntityManager.

I was thinking of creating a mock QueryRunner, but then I need to pass this mocked QueryRunner to the mocked EntityManager somehow (it’s readonly so I can’t just assign it). And I’m pretty sure if I go down that line, I assume I have to mock the start, commit, and rollback transaction calls of the QueryRunner.

I (hope) might just be missing something much more obvious and simple

LEAVE A COMMENT