What is the best way to test a Nest Bull queue using Jest with dependency injection through @InjectQueue?

When working with an Injectable that utilizes a queue through the @InjectQueue decorator:

@Injectable()
export class EnqueuerService {
  constructor (
    @InjectQueue(QUEUE_NAME) private readonly queue: Queue
  ) {
  }

  async foo () {
    return this.queue.add('job')
  }
}

I am wondering how I can effectively test if this service interacts with the queue correctly. Setting up the basic structure:

describe('EnqueuerService', () => {
  let module: TestingModule
  let enqueuerService: EnqueuerService

  beforeAll(async () => {
    module = await Test.createTestingModule({
      imports: [EnqueuerModule]
    }).compile()
    enqueuerService = module.get(EnqueuerService)

    // In a typical case, we would pull in the dependency for testing:
    // queue = module.get(QUEUE_NAME)
    //
    // (but this doesn't work due to the @InjectQueue decorator on queue)
  })

  afterAll(async () => await module.close())

  describe('#foo', () => {
    it('adds a job', async () => {
      await enqueuerService.foo()

      // It would be great to have something like:
      // expect(queue.add).toBeCalledTimes(1)
      //
      // (but there might be simpler alternatives?)
    })
  })
})

I'm struggling with the Nest DI container configuration and I feel like there must be a smart solution out there. Despite numerous attempts and no luck with the documentation, I'm seeking help from anyone who might have a solution. It's not necessary to use mocking; creating a real queue for testing purposes is also acceptable. My main goal is to ensure that my service queues tasks as intended! Any assistance would be greatly appreciated.

Answer №1

Upon examining the library, I stumbled upon a useful function that generates the queue injection token based on its name: getQueueToken(name?: string).

This function is exported from the library, allowing you to utilize it for providing your own queue implementation during testing.

import { getQueueToken } from '@nestjs/bull';

describe('EnqueuerService', () => {
  let module: TestingModule
  let enqueuerService: EnqueuerService

  beforeAll(async () => {
    module = await Test.createTestingModule({
      imports: [EnqueuerModule]
    })
      .overrideProvider(getQueueToken(QUEUE_NAME))
      .useValue({ /* mocked queue */ })
      .compile()

    enqueuerService = module.get(EnqueuerService)
    queue = module.get(QUEUE_NAME)
  })

  afterAll(async () => await module.close())

  describe('#foo', () => {
    it('adds a job', async () => {
      await enqueuerService.foo()

      expect(queue.add).toBeCalledTimes(1)
    })
  })
})

UPDATE

If you want to monitor the methods invoked on the queue service, you can create a mock at the beginning of your test file. I've restructured the test file to have a clearer division between setting up the test and executing the test:

let module: TestingModule
let enqueuerService: EnqueuerService
let mockQueue;

// Create a utility function to establish the application.
const createApp = async () => {
  module = await Test.createTestingModule({
    imports: [EnqueuerModule]
  })
    .overrideProvider(getQueueToken(QUEUE_NAME))
    .useValue(mockQueue)
    .compile()

  enqueuerService = module.get(EnqueuerService)
}

describe('EnqueuerService', () => {    
  // Reset the app before each test to clear the mock.
  beforeEach(async () => {
    mockQueue = {
      add: jest.fn(),
    };
    await createApp();
  })

  afterAll(async () => await module.close())

  describe('#foo', () => {
    it('adds a job', async () => {
      await enqueuerService.foo()

      // Verify calls made to the mock.
      expect(mockQueue.add).toBeCalledTimes(1)
    })
  })

  describe('#bar', () => {
    // Modify the mock for a specific test suite.
    beforeEach(async () => {
      mockQueue.add = jest.fn().mockImplementation(/** */);
      // Since the mock has changed, recreate the app to apply the new value.
      await createApp();
    })

    it('adds a job', async () => {
      // ...
    })
  })
})

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Declaring scoped runtime interfaces with Typescript

I need to create a global interface that can be accessed at runtime under a specific name. /** Here is my code that will be injected */ // import Vue from "vue"; <- having two vue instances may cause issues // ts-ignore <- Vue is only ava ...

Issues with TypeScript arise when transferring arguments between functions

Encountering a TypeScript error due to this pattern: Error message: 'Argument of type '(string | number)[]' is not assignable to parameter of type 'string[] | number[]' function foo(value: string | number) { return bar([va ...

Strategies for persisting data in React using local storage to ensure information is retained after page refresh

I am attempting to store searched recipes data in local storage so that when the user refreshes, the data from the last searched recipe will still be available. I have tried saving the recipes data to local storage when a new search request is made and se ...

What could be causing the API link to not update properly when using Angular binding within the ngOnInit method?

Hi there, I'm currently working on binding some data using onclick events. I am able to confirm that the data binding is functioning properly as I have included interpolation in the HTML to display the updated value. However, my challenge lies in upd ...

Opening a modal in Angular2+ when selecting an item from ngx-chips (tag-input)

I need to implement a functionality where clicking on a tag in a dropdown should trigger the opening of a modal window in my Angular application. Below is the code snippet related to this feature: <div class="force-to-the-bottom"> <tag-input [ ...

Deciphering the TypeScript type in question - tips and tricks

One of my abstract classes includes a static property with various properties, where default is consistently named while the others may have random names. public static data = { default: { //only this one always have 'dafault' name na ...

In Typescript, the use of an array as a class member is not defined

I'm having trouble adding an array as a class member in my Typescript code. When I try to access it in a certain function, I keep getting an error saying it's undefined. What am I doing wrong? .html <button id="storePersonBtn"> .ts exp ...

When working with TypeScript, it's important to note that an implicit 'any' type may occur when trying to use an expression of type 'string' to index a specific type

Currently, I'm attempting to transfer a custom hook used in Vue for handling i18n from JavaScript to TypeScript. However, I am encountering a persistent error message: An element is implicitly assigned the type 'any' due to an expression o ...

Ways to display the ping of a game server on your screen

Is there a way to display the game server's ping on the screen like in the example below? this.tfEnter.text = ShowPing + " ms"; Sometimes the code snippets provided in examples may not function properly. Channel List Image: https://i.stack ...

Page Breaks - Patience in anticipation of dataSource readiness

I am facing an issue with my pagination service and component. The dataSource appears empty when the page is loaded for the first time, but by the second time it is populated and I can display the dataTable and paginate successfully. Is there a workaround ...

Why is the removal of this type assertion in TypeScript causing an issue?

Why is TypeScript refusing to compile this code snippet? interface TaggedProduct { tag: string; } interface Product { tag?: string; } const tagProduct = (product: Product): TaggedProduct => { const tag: string = "anything"; pro ...

Data binding not being subscribed to the input decorator

My local variable is not being bound properly when retrieving data from a subscription. In my setup, I have two components and a service. The parent component triggers a method in the service to make an HTTP GET request, which includes a user object that ...

Is it possible to access the passed arguments in the test description using jest-each?

Utilizing TypeScript and Jest, consider this sample test which can be found at https://jestjs.io/docs/api#testeachtablename-fn-timeout it.each([ { numbers: [1, 2, 3] }, { numbers: [4, 5, 6] } ])('Test case %#: Amount is $numbers.length =&g ...

What is the reason for TypeScript's refusal to accept this task?

In my attempt to create a type that can be A, B, or an object with a key containing an array of 2 or more items that are either A, B, or another similar object (thus allowing for recursive definition). This is the solution I came up with: type A = { p ...

enhancing the types of parameters in a function declaration without relying on generics

My goal is to improve developer experience (DX) by expanding the types for parameters in a function signature. I want the tooltip when hovering over the following function to provide more detailed information: type Props = { a: number; }; const func = ( ...

What are the steps for conducting a component test with material ui?

My current component is built using . import React from 'react'; import { AppBar, Toolbar } from 'material-ui'; import { Typography } from 'material-ui'; import { MuiThemeProvider, createMuiTheme } from 'material-ui/sty ...

How to simulate loadStripe behavior with Cypress stub?

I am struggling to correctly stub out Stripe from my tests CartCheckoutButton.ts import React from 'react' import { loadStripe } from '@stripe/stripe-js' import useCart from '~/state/CartContext' import styles from '. ...

The i18next-http-backend module could not be loaded

After implementing the code below to create the module 'i18next-http-backend' (installed version: "i18next-http-backend": "^1.4.1"), I encountered an issue where the module cannot be loaded. The browser console displayed the e ...

Difficulty locating the module in Typescript/Javascript

Currently facing an issue while trying to incorporate a module called "request" into my Angular 2 Typescript project from here. Despite following the usual installation process with npm install --save request and also attempting typings install request -- ...

Error: Attempting to access the 'tokenType' property of an undefined object is not allowed

We encountered an error while attempting to embed a report using the Power BI Angular library. TypeError: Cannot read properties of undefined (reading 'tokenType') at isSaaSEmbedWithAADToken (reportEmbed?navContentPaneEnabled=false&uid=am ...