Search code examples
node.jsunit-testingrabbitmqtddts-jest

NodeJS Unit Test RabbitMQ / Amqplib


I am trying to develop tests for my project and I have a file that connects to rabbitmq and consumes a queue, but i'm having problems to have idea to test it

const amqp = require('amqplib/callback_api');

const rabbitConsumer = (io) => {
  setTimeout(() => {
    amqp.connect('amqp://rabbitmq', (error0, connection) => {
      if (error0) {
        throw error0;
      }
      connection.createChannel((error1, channel) => {
        if (error1) {
          throw error1;
        }
        const queue = 'message';

        channel.assertQueue(queue, {
          durable: false,
        });

        console.log(' [*] Waiting for message', queue);

        channel.consume(
          queue,
          (data) => {
            console.log(' [x] Received data:', data.content.toString('utf-8'));
            io.emit('sendMessage', data.content.toString('utf-8'));
          },
          {
            noAck: true,
          }
        );
      });
    });
  }, 10000);
};

module.exports = rabbitConsumer;

Is possible to test this file? How could I do this using JEST or any other library?


Solution

  • You can use jest.spyOn(object, methodName) to create mock for amqp object's methods.

    Use jest.useFakeTimers(implementation?: 'modern' | 'legacy') to tell jest uses fake versions of setTimeout function so that you don't need to wait for real delay time.

    Use jest.advanceTimersByTime(msToRun) to

    When this API is called, all timers are advanced by msToRun milliseconds. All pending "macro-tasks" that have been queued via setTimeout() or setInterval(), and would be executed within this time frame will be executed

    E.g.

    index.js:

    const amqp = require('amqplib/callback_api');
    
    const rabbitConsumer = (io) => {
      setTimeout(() => {
        amqp.connect('amqp://rabbitmq', (error0, connection) => {
          if (error0) {
            throw error0;
          }
          connection.createChannel((error1, channel) => {
            if (error1) {
              throw error1;
            }
            const queue = 'message';
            channel.assertQueue(queue, { durable: false });
            console.log(' [*] Waiting for message', queue);
            channel.consume(
              queue,
              (data) => {
                console.log(' [x] Received data:', data.content.toString('utf-8'));
                io.emit('sendMessage', data.content.toString('utf-8'));
              },
              { noAck: true }
            );
          });
        });
      }, 10000);
    };
    
    module.exports = rabbitConsumer;
    

    index.test.js:

    const amqp = require('amqplib/callback_api');
    const rabbitConsumer = require('./');
    
    describe('rabbitConsumer', () => {
      beforeAll(() => {
        jest.useFakeTimers();
      });
      afterAll(() => {
        jest.useRealTimers();
      });
      test('should pass', () => {
        const mData = {
          content: 'teresa teng',
        };
        const mChannel = {
          assertQueue: jest.fn(),
          consume: jest.fn().mockImplementation((queue, callback) => {
            callback(mData);
          }),
        };
        const mConnection = {
          createChannel: jest.fn().mockImplementation((callback) => {
            callback(null, mChannel);
          }),
        };
        jest.spyOn(amqp, 'connect').mockImplementation((url, callback) => {
          callback(null, mConnection);
        });
        const mIO = {
          emit: jest.fn(),
        };
        rabbitConsumer(mIO);
        jest.advanceTimersByTime(10000);
        expect(amqp.connect).toBeCalledWith('amqp://rabbitmq', expect.any(Function));
        expect(mConnection.createChannel).toBeCalledWith(expect.any(Function));
        expect(mChannel.assertQueue).toBeCalledWith('message', { durable: false });
        expect(mChannel.consume).toBeCalledWith('message', expect.any(Function), { noAck: true });
      });
    });
    

    test result:

     PASS  examples/69715530/index.test.js (21.564 s)
      rabbitConsumer
        ✓ should pass (27 ms)
    
      console.log
         [*] Waiting for message message
    
          at examples/69715530/index.js:15:17
    
      console.log
         [x] Received data: teresa teng
    
          at channel.consume.noAck (examples/69715530/index.js:19:21)
    
    ----------|---------|----------|---------|---------|-------------------
    File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    ----------|---------|----------|---------|---------|-------------------
    All files |    87.5 |       50 |     100 |    87.5 |                   
     index.js |    87.5 |       50 |     100 |    87.5 | 7,11              
    ----------|---------|----------|---------|---------|-------------------
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   0 total
    Time:        23.222 s
    

    You can create mocked errors and pass them to the callback of mock implementation to test the error handler branch.