javascriptnode.jsunit-testingmockingjestjs

How to test nested mocked functions


How can I write tests on mocked functions inside a mocked function? I want to test that my publish mocked function is called once.

jest.mock('amqplib', () => ({
  connect: jest.fn(() => Promise.resolve({
    createChannel: jest.fn(() => Promise.resolve({
      assertExchange: jest.fn(),
      publish: jest.fn(),
    })),
    close: jest.fn(),
  })),
}));

The actual function I want to test.

export default function (key, data, exchange = 'portal.topic', options = { type: 'topic' }) {
  return amqp.connect(`amqp://${RABBITMQ_USER}:${RABBITMQ_PASS}@${RABBITMQ_URL}:${RABBITMQ_PORT}`).then(conn => {
    conn.createChannel().then((ch) => {
      ch.assertExchange(exchange, options.type, { durable: true });
      ch.publish(exchange, key, Buffer.from(JSON.stringify(data)));
    });
    setTimeout(() => { conn.close(); }, 1000);
  });
}

Solution

  • First of all, I recommend removing setTimeout from your code based on your logic, and the conn.createChannel method does not form a promise chain with the outer promise. After changing, the unit test:

    index.js:

    import amqp from 'amqplib';
    
    const RABBITMQ_USER = 'RABBITMQ_USER';
    const RABBITMQ_PASS = 'RABBITMQ_PASS';
    const RABBITMQ_URL = 'RABBITMQ_URL';
    const RABBITMQ_PORT = 'RABBITMQ_PORT';
    
    export default function(key, data, exchange = 'portal.topic', options = { type: 'topic' }) {
      return amqp
        .connect(`amqp://${RABBITMQ_USER}:${RABBITMQ_PASS}@${RABBITMQ_URL}:${RABBITMQ_PORT}`)
        .then((conn) => {
          return conn.createChannel().then((ch) => {
            ch.assertExchange(exchange, options.type, { durable: true });
            ch.publish(exchange, key, Buffer.from(JSON.stringify(data)));
            return conn;
          });
        })
        .then((conn) => conn.close());
    }
    

    index.test.js:

    import createChannel from './';
    import amqp from 'amqplib';
    
    describe('44922162', () => {
      afterAll(() => {
        jest.restoreAllMocks();
      });
      it('should create channel correctly', async () => {
        const mCh = { assertExchange: jest.fn(), publish: jest.fn() };
        const mConn = { createChannel: jest.fn().mockResolvedValueOnce(mCh), close: jest.fn() };
        const connectSpy = jest.spyOn(amqp, 'connect').mockResolvedValueOnce(mConn);
        const data = { payload: '123' };
        await createChannel('key', data);
        expect(connectSpy).toBeCalledWith('amqp://RABBITMQ_USER:RABBITMQ_PASS@RABBITMQ_URL:RABBITMQ_PORT');
        expect(mConn.createChannel).toBeCalledTimes(1);
        expect(mCh.assertExchange).toBeCalledWith('portal.topic', 'topic', { durable: true });
        expect(mCh.publish).toBeCalledWith('portal.topic', 'key', Buffer.from(JSON.stringify(data)));
        expect(mConn.close).toBeCalledTimes(1);
      });
    });
    

    unit test result with coverage report:

     PASS  src/stackoverflow/44922162/index.test.js (11.008s)
      44922162
        ✓ should create channel correctly (7ms)
    
    ----------|----------|----------|----------|----------|-------------------|
    File      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
    ----------|----------|----------|----------|----------|-------------------|
    All files |      100 |      100 |      100 |      100 |                   |
     index.js |      100 |      100 |      100 |      100 |                   |
    ----------|----------|----------|----------|----------|-------------------|
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   0 total
    Time:        12.335s
    

    source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/44922162