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.

export class InvoiceService implements OnApplicationBootstrap {
    private dbManager: EntityManager,
    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)

    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

    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 (

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