Mocking Bull queues in NestJS
Asked Answered
P

4

7

I am trying to test that after sending a request to one of my controllers, the queue pushes a job. The implementation itself works as expected.

This is my app.module.ts

        @Module({
        imports: [
        HttpModule,
        TypeOrmModule.forRoot(typeOrmConfig),
        BullModule.forRoot({
          redis: {
            host: redisConfig.host,
            port: redisConfig.port,
          },
        }),
        // Bunch of unrelated modules
         ],
         providers: [
        {
          provide: APP_FILTER,
          useClass: AllExceptionsFilter,
        },
        ],
         controllers: [SomeControllers],
        })
        export class AppModule {}

And this is how my import.module.ts (module using queues) looks like:

    @Module({
      imports: [
        BullModule.registerQueue({
          name: importQueueName.value,
        }),
       //More unrelated modules,
      ],
      providers: [
        //More services, and bull consumer and producer,
        ImportDataProducer,
        ImportDataConsumer,
        ImportDataService,
      ],
      controllers: [ImportDataController],
    })
    export class ImportDataModule {}

I tried to follow this approach

Which does not register the queue in the beforeAll hook, and I'm getting

     Driver not Connected

And this approach

Which registers a queue in the beforeAll hook in the test suite, and I am getting:

     TypeError: Cannot read properties of undefined (reading 'call')
    
          at BullExplorer.handleProcessor (node_modules/@nestjs/bull/dist/bull.explorer.js:95:23)
          at MapIterator.iteratee (node_modules/@nestjs/bull/dist/bull.explorer.js:59:26)
          at MapIterator.next (node_modules/iterare/src/map.ts:9:39)
          at FilterIterator.next (node_modules/iterare/src/filter.ts:11:34)
          at IteratorWithOperators.next (node_modules/iterare/src/iterate.ts:19:28)
              at Function.from (<anonymous>)
          at IteratorWithOperators.toArray (node_modules/iterare/src/iterate.ts:227:22)
          at MetadataScanner.scanFromPrototype (node_modules/@nestjs/core/metadata-scanner.js:12:14)
          at node_modules/@nestjs/bull/dist/bull.explorer.js:56:34
              at Array.forEach (<anonymous>)

This is my 'base test suite':

    describe('Queue test suite', () => {
      let app: INestApplication;
      const importQueue: any = { add: jest.fn() };
      beforeAll(async () => {
        const moduleFixture: TestingModule = await Test.createTestingModule({
          imports: [AppModule, ImportDataModule],
        })
          .overrideProvider(importQueueName.value)
          .useValue(importQueue)
          .compile();
    
        app = moduleFixture.createNestApplication();
        app.useGlobalPipes(
          new ValidationPipe({
            transform: true,
            whitelist: true,
            forbidNonWhitelisted: true,
          }),
        );
        await app.init();
      });
    
      afterAll(async () => {
       
        await app.close();
     
      });
    
      test('A job should be pushed', async () => {
        await request(app.getHttpServer())
          .post('/some/route')
          .attach('file', __dirname + '/some.file')
          .expect(HttpStatus.CREATED);
       
    
        expect(importQueue.add).toHaveBeenCalled();
      });
    });

Any idea what could be wrong here?

Pepin answered 3/1, 2022 at 9:32 Comment(0)
E
5

I had the same issue, the problem is with your mockQueue. You need to add a process mocked function.

This should work for you!

const importQueue: any = { 
  add: jest.fn(),
  process: jest.fn(),
};

And this is how I tested it.

expect(mockQueue.add).toBeCalledTimes(1);
      expect(mockQueue.add).nthCalledWith(
        1,
        PendoJobNames.SCR,
        {
          ...mockJobDto,
        },
        {
          jobId: mockDto.visitorId,
          removeOnComplete: true,
          removeOnFail: true,
        },
      );
    ```
Emilyemina answered 20/4, 2022 at 19:26 Comment(0)
T
3

I found a simple way to deep mock a class or an interface using createMock function from the package @golevelup/ts-jest, which works well with NestJS dependency injection.

  let someController: SomeController;
  let someService: DeepMocked<SomeService>;

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [SomeModule],
      controllers: [SomeController],
      providers: [
        {
          provide: SomeService,
          useValue: createMock<SomeService>(),
        },
        { provide: getConnectionToken(), useValue: createMock<Connection>() },
        { provide: getQueueToken('queueName'), useValue: createMock<Queue>() },
      ],
    }).compile();

    someController = moduleRef.get<SomeController>(SomeController);
    someService = moduleRef.get(SomeService);
  });

Tepic answered 13/6, 2023 at 22:49 Comment(0)
M
2
import { createMock } from "@golevelup/ts-jest";
import { getQueueOptionsToken, getQueueToken } from "@nestjs/bullmq";
import { Test } from "@nestjs/testing";
import { Queue, QueueOptions } from "bullmq";

describe("SomeModule", () => {
  let module: SomeModule;

  beforeEach(async () => {
    const testingModule = await Test.createTestingModule({
      imports: [
        /* ... */
      ],
    })
      .overrideProvider(getQueueOptionsToken())
      .useValue(createMock<QueueOptions>())
      .overrideProvider(getQueueToken("yourQueueName"))
      .useValue(createMock<Queue>())
      .compile();

    module = testingModule.get(SomeModule);
  });

  it("can be instantiated by Nest", () => {
    expect(module).toBeDefined();
  });
});
Matadi answered 27/7, 2023 at 9:37 Comment(0)
M
0

The answer by @moogs is correct, but I had to add another function as well.

const queueMock = {
  add: jest.fn(),
  process: jest.fn(),
  on: jest.fn()
};

The on function, and it worked.

Micrometry answered 3/9, 2022 at 19:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.