Search code examples
mysqlnode.jsunit-testingaws-lambdasinon

Unit test mysql query with callback using sinon


I have class that is used to create a mysql connection pool and insert records in a database. The inserts work correctly but I'm having trouble getting full coverage through unit tests.

Here is the Connection class:

const mysql2 = require('mysql2');

class Connection {
    constructor(options = {}) {
        this.options = options;
    }

    createPool () {
        this.pool = mysql2.createPool({
            host: this.options.host,
            user: this.options.user,
            database:  'my_database',
            ssl: 'Amazon RDS',
            password: this.options.password,
            authPlugins: {
                mysql_clear_password: () => () => Buffer.from(this.options.password + '\0')
            }
        });
    }

    async insert (sql, values) {
        const promise = new Promise( (resolve, reject) => {

            try {
                this.pool.query(sql, [values],function (error, results, fields) {
                    if (error) throw error;
                    console.log(results.affectedRows); //Show 1
                    resolve (results.affectedRows);
                });
            } catch (e) {
                reject(e);
            }

        })
        return promise;
    }
}

module.exports = { Connection };

And here is my failing test:

const conns = require('../src/connection');
const sinon = require('sinon');
const mysql2 = require('mysql2');

describe('handler', () => {

    test('Test insert from Connection', async () => {
        const options = {
            host: 'testHost',
            user: 'testUser',
            password: 'testPassword'
        };

        const poolStub = {
            getConnection: sinon.stub().returnsThis(),
            query: sinon.stub().returnsThis(),
        };

        const createPoolStub = sinon.stub(mysql2, 'createPool').returns(poolStub);
        const conn = new conns.Connection(options);
        await conn.createPool();
        await conn.insert( 'select 1 + 1 as solution', [])

        sinon.restore();
    });
});

The test just times out.

: Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.

I'm pretty sure that I'm not stubbing query correctly in the poolStub, but I'm not sure how to account for the callback method. How can I unit test this insert method so i can have full coverage?


Solution

  • Here is the unit test solution:

    connection.js:

    const mysql2 = require('mysql2');
    
    class Connection {
      constructor(options = {}) {
        this.options = options;
      }
    
      createPool() {
        this.pool = mysql2.createPool({
          host: this.options.host,
          user: this.options.user,
          database: 'my_database',
          ssl: 'Amazon RDS',
          password: this.options.password,
          authPlugins: {
            mysql_clear_password: () => () => Buffer.from(this.options.password + '\0'),
          },
        });
      }
    
      async insert(sql, values) {
        const promise = new Promise((resolve, reject) => {
          try {
            this.pool.query(sql, [values], function(error, results, fields) {
              if (error) throw error;
              console.log(results.affectedRows); //Show 1
              resolve(results.affectedRows);
            });
          } catch (e) {
            reject(e);
          }
        });
        return promise;
      }
    }
    
    module.exports = { Connection };
    

    connection.test.js:

    const conns = require('./connection');
    const sinon = require('sinon');
    const mysql2 = require('mysql2');
    const { expect } = require('chai');
    
    describe('handler', () => {
      it('Test insert from Connection', async () => {
        const options = {
          host: 'testHost',
          user: 'testUser',
          password: 'testPassword',
        };
    
        const results = { affectedRows: 1 };
        const poolStub = {
          getConnection: sinon.stub().returnsThis(),
          query: sinon.stub().callsFake((sql, values, callback) => {
            callback(null, results);
          }),
        };
    
        const createPoolStub = sinon.stub(mysql2, 'createPool').returns(poolStub);
        const conn = new conns.Connection(options);
        conn.createPool();
        const actual = await conn.insert('select 1 + 1 as solution', []);
        expect(actual).to.be.eql(1);
        sinon.assert.calledWithExactly(poolStub.query, 'select 1 + 1 as solution', [[]], sinon.match.func);
        sinon.assert.calledWithExactly(createPoolStub, {
          host: 'testHost',
          user: 'testUser',
          password: 'testPassword',
          database: 'my_database',
          ssl: 'Amazon RDS',
          authPlugins: {
            mysql_clear_password: sinon.match.func,
          },
        });
        sinon.restore();
      });
    });
    

    unit test result with coverage report:

      handler
    1
        ✓ Test insert from Connection
    
    
      1 passing (15ms)
    
    ---------------|---------|----------|---------|---------|-------------------
    File           | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    ---------------|---------|----------|---------|---------|-------------------
    All files      |   73.33 |    33.33 |   71.43 |   84.62 |                   
     connection.js |   73.33 |    33.33 |   71.43 |   84.62 | 16,30