user4002112
user4002112

Reputation: 181

How to mock promisify call on mysql query nodeJS using sinon and Mocha?

This is my code using mysql -

import * as mysql from 'mysql';
import {promisify} from 'util';


 const connectionParams:any = {
     /* set as environment variables */
     host: host,
     user: user,
     password: password,
     port: parseInt(port)
 };
 var connection:any;
 const getRecords = async (inputValue: string) => {

//validate inputValue

const userIds: string[] = [];
logger.info("Creating mysql connection");
try {
    connection = mysql.createConnection(connectionParams);
    const query = promisify(connection.query).bind(connection);
    const queryResult = await query({ sql: sqlQuery, timeout: 1000, values: value1, inputValue] });
    if (queryResult) {
        queryResult.forEach((row) => {
            userIds.push(row.userid);
        });
    }
} catch (error) {
    logger.info(error);
    // console.log(error);
    throw new Error('Could not retrieve user IDs');
} finally {
    connection.end();
}
return userIds;
 };

And this is my test -

it('should return a list of records when right inputs are given', async() => {
        sinon.stub(process, 'env').value({
            'database': 'TESTDB'
        });
        let dummyArray = [{ userid: 'xyz' }];
        let createConnection = {
            connect: function(connectionParams: any) {
                return Promise.resolve()
            },
            query : sinon.stub().withArgs({}).callsFake(function (...args): Promise<Object>{
                const dummyArray = [{ userid: 'xyz' }];
                return new Promise(function(resolve){resolve(dummyArray)});
            }),
            end: function() {}
        };
        let mySqlStub = {
            createConnection: sinon.stub().returns(createConnection)
        };
        const dbops = proxyquire('../../lib/dbops', {'mysql': mySqlStub}).default;
        expect(await dbops.getUserIds('Delete')).to.deep.equal(['xyz']);
    });

How do I write the fake function for the query?

query : sinon.stub().withArgs({}).callsFake(function (...args): Promise{ const dummyArray = [{ userid: 'xyz' }]; return new Promise(function(resolve){resolve(dummyArray)}); })

This does not work for me. How can I get this to work? I cannot get the stub function to resolve and return the intended value in the main function. The query just hangs and throws an error after the timeout. The error is happening in "matchingfakes" method within the stub.

Upvotes: 1

Views: 887

Answers (1)

Lin Du
Lin Du

Reputation: 102277

proxyquire is used for stubbing the standalone function exports from a module or package. Since mysql is an object, you can stub its methods by sinon.stub(obj, 'method'). You don't need to use use proxyquire package.

Even if you use util.promisify to generate promise versions for the NodeJS error-First callback method(mysql.query(sql, callback), the callback signature is function (error, results, ...args): void). You need to use .callsFake() to create a mock implementation for this method, and trigger the promise version by calling its callback.

And, you should import the function after stubbing the environment variables. Because when you import the ./dbops module, the code in the module scope will be executed immediately, at this time, the environment variables are not stubbed.

E.g.

dbops.ts:

import mysql from 'mysql';
import { promisify } from 'util';

const connectionParams: any = {
  host: process.env.HOST,
  user: process.env.USER,
  password: process.env.PASSWORD,
  port: parseInt(process.env.PORT || '3306'),
};
var connection: any;

const getRecords = async (inputValue: string) => {
  const sqlQuery = 'SELECT * FROM tests';
  const value1 = '';
  const userIds: string[] = [];
  console.info('Creating mysql connection');
  try {
    connection = mysql.createConnection(connectionParams);
    const query = promisify(connection.query).bind(connection);
    const queryResult = await query({ sql: sqlQuery, timeout: 1000, values: value1, inputValue });
    if (queryResult) {
      queryResult.forEach((row) => {
        userIds.push(row.userid);
      });
    }
  } catch (error) {
    console.info(error);
    throw new Error('Could not retrieve user IDs');
  } finally {
    connection.end();
  }
  return userIds;
};

export { getRecords };

dbops.test.ts:

import sinon from 'sinon';
import mysql from 'mysql';

describe('69702002', () => {
  it('should return a list of records when right inputs are given', async () => {
    sinon.stub(process, 'env').value({
      HOST: '127.0.0.1',
      USER: 'testuser',
      PASSWORD: 'testpwd',
      PORT: '3306',
    });
    const { getRecords } = await import('./dbops');
    const dummyArray = [{ userid: 'xyz' }];

    let connectionStub = {
      query: sinon.stub().callsFake((sql, callback) => {
        callback(null, dummyArray);
      }),
      end: sinon.stub(),
    };
    sinon.stub(mysql, 'createConnection').returns(connectionStub);
    const actual = await getRecords('test input');
    sinon.assert.match(actual, ['xyz']);
    sinon.assert.calledWithExactly(mysql.createConnection, {
      host: '127.0.0.1',
      user: 'testuser',
      password: 'testpwd',
      port: 3306,
    });
    sinon.assert.calledOnce(connectionStub.end);
  });
});

test result:

 69702002
Creating mysql connection
    ✓ should return a list of records when right inputs are given (945ms)


  1 passing (952ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |   90.48 |       50 |     100 |      90 |                   
 dbops.ts |   90.48 |       50 |     100 |      90 | 27-28             
----------|---------|----------|---------|---------|-------------------

Upvotes: 1

Related Questions