I am having a hard time using mocks for BullMQ in NodeJS with Typescript. I follow the instructions, the very related example and this question.
The test case focuses on the initial setup that instantiates 3 Queues, adds a job in each of them and starts the corresponding workers. Note that the source code instantiates a Queue with: import {Queue} from 'bullmq'; and const newQueue = new Queue(...). Then job is added with newQueue.add(...).
Problem: I cannot use a reusable mock object that can be defined in __mocks__ directory (Manual mock in the documentation).
I use:
const mockAdd = jest.fn(() => console.log('test'));
const mockQueue = jest.fn(function () {
return {
add: mockAdd,
};
});
const mockBullmq = jest.fn(function () {
return {
Queue: mockQueue,
};
});
export { mockAdd, mockQueue, mockBullmq as default};
The result is TypeError: bullmq_1.Queue is not a constructor.
Because I use typescript ("target": "ES2020") with named import I also followed the advice in the first answer here with no luck. The used code follows:
const mockAdd = jest.fn(() => console.log('test'));
const mockQueue = jest.fn(function () {
return {
add: mockAdd,
};
});
const mockBullmq = jest.mock('./bullmq', function () {
return {
Queue: mockQueue,
};
});
export { mockAdd, mockQueue, mockBullmq as default};
The test case is the following:
import queue from '@worker/queue';
import { Queue, Worker, add } from '@mocks/bullmq';
describe('Test queue setup:', () => {
it('should connect to queues', async () => {
await queue.setup();
expect(Queue).toHaveBeenCalledTimes(3);
expect(Queue.mock.instances).toHaveLength(3);
expect(add).toHaveBeenCalledTimes(3);
expect(Queue.mock.instances[0].add).toHaveBeenCalledTimes(1);
expect(Worker).toHaveBeenCalledTimes(3);
});
});
No, you kinda had the right idea from my comment but not exactly. Honestly, you really need to read through the mocks docs and see how they say to do it because there are so many nuances based on each use case.
The general idea for mocking
node_modulesis that any matching module inside of__mocks__/is automatically used to mock the module in all tests regardless of whetherautomockistrueor whether you explicitly calljest.mock('my-module')or not. See docs here for more details.So taking a look at your case I am going to simplify things a bit because you didn't provide the
setuplogic, but I'm confident you can apply this to your exact case. But imagine I have thissetup.tsfile I want to test below...Then the tests I would then write as such...
There are a few key things to note here. The first is how I import the
bullmqmocks. Even though I am importing frombullmqas I normally would, this is actually importing the module from__mocks__/bullmqif it exists, I'll get to this file later. But you don't import directly from__mocks__/jest rewires this import under the hood. This applies to anyimportof this module in both tests andsrc/files!The next thing is why am I importing
mocksfrom../__mocks__/bullmq? I'll explain this more later but this is essentially a way to import other things that are not exports of the actualbullmqmodule.Next is the
jest.mock('bullmq')line, as I mentioned in the beginning this is not needed for mockingnode_modules, see docs.Next is the
(Queue as unknown as jest.Mock)type gymnastics, this is a way to coerce the default import type to be of the typeMock. The issue is that by importing the module directly from thebullmqthe original types are used not the mocked types. But since we know there is a mock we must override this type as the underlying javascript object is in fact a mock, it just doesn't know it. There may be a better way to handle this but this is the quick and easy way.Finally I removed the line you had as...
In order to access the instance methods you must build the mock differently by applying the methods to the
thiscontext, see the SO question for more.So now for the
__mocks__file. Here the file name must match the module name, I think you could also do__mocks__/bullmq/index.tsif you'd like to organize your mock better.Notice the
addmethod is shared across all instances that are created using theQueuemock. Then I just add all these erroneous exports undermocksexport. If I want to access theaddmethod from inside a test file, I can import it usingimport { mocks } from '../__mocks__/bullmq'and using it frommocks.add, see how I did this insetup.test.ts. If you were to importaddfrombullmqyou would get a type error as the original types don't have this export.Back to the line about
expect(Queue.mock.instances[0].add).toHaveBeenCalledTimes(1), I removed this as this would produce the same mock instance as themocks.addfor all instances ofQueue. So maybe you can tweak this to assert what you wanted but it doesn't make sense as it was.A helpful tip is adding
console.login your mocks to debug and ensure that they are actually being called.I created a demo replit here. You can open the shell tab on the right and run
npm run testto see the tests pass. The output is shown as...