andreas
andreas

Reputation: 1595

Set up a mocha tests with sinon mocks, with mysql and bluebird promises

I have her a project with following setup: JavaScript ES6 (transpiled with Babel), mocha tests, MySql access with node-mysql and Bluebird Promises.

Maybe using Bluebird together with Babel/ES6 already is my first issue, but let's explain the situation and the problem:

My DBRepository Object:

let XDate = require('xdate'),
  _ = require('lodash');
const Promise = require("bluebird");
const debug = require('debug')('DBRepository');

class DBRepository {

  constructor(mysqlMock) {
    "use strict";
    this.mysql = mysqlMock;
    if( this.mysql == undefined) {
      debug('init mysql');
      this.mysql = require("mysql");
      Promise.promisifyAll(this.mysql);
      Promise.promisifyAll(require("mysql/lib/Connection").prototype);
      Promise.promisifyAll(require("mysql/lib/Pool").prototype);
    }

    this.config = {
      connectionLimit: 10,
      driver: 'pdo_mysql',
      host: 'my_sql_container',
      port: 3306,
      user: 'root',
      password: '**********',
      testDbName: 'db-name'
    };
    this.pool = this.mysql.createPool(this.config); // <== Here the error is thrown
  }

  getSqlConnection() {
    return this.pool.getConnectionAsync().disposer(function (connection) {
      try {
        connection.release();
      } catch (e) {
        debug('Error on releasing MySQL connection: ' + e);
        debug(e.stack);
      }
    });
  }

  getGoods(queryParams) {
    "use strict";

    if (queryParams === undefined) {
      queryParams = {};
    }
    if (queryParams.rowCount === undefined) {
      queryParams.rowCount = 15;
    }

    let query = "SELECT id, name FROM my_table";
    return Promise.using(this.getSqlConnection(), (conn => {
      debug('query: ' + query);
      return conn.queryAsync(query);
    }));
  }
}

This code works fine for me in my normal code, but when I try to use int in a mocha test, with sinon for mocking I get following error TypeError: this.mysql.createPool is not a function

This is my test code:

let expect = require('chai').expect,
  XDate = require('xdate'),
  _ = require('lodash'),
  sinon = require('sinon'),
  Promise = require('bluebird'),
  toBeMocketMySql = require('mysql');

Promise.promisifyAll(toBeMocketMySql);
Promise.promisifyAll(require("mysql/lib/Connection").prototype);
Promise.promisifyAll(require("mysql/lib/Pool").prototype);

describe(".inflateOffers(offerPCs, offerGroups)", () => {
  "use strict";

  it('should inflate Offers (with all OfferGroups and a PricingCluster from db rows.', () => {

    let offerPCs = JSON.parse('[... some objects ...]');
    let offerGroups = JSON.parse('[... some objects ...]');
    let mock = sinon.mock(toBeMocketMySql);
    let dbRepo = new DBRepository(mock); // <== Here the error is thrown


    let offers = dbRepo.inflateObjects(offerPCs, offerGroups);
    expect(offers).to.be.an('object')
      .and.to.be.an('array')
      .to.have.length(1);

    expect(offers[0]).to.be.an('object')
      .and.not.to.be.an('array')
      .to.have.property('a')
      .to.have.property('b');

  });
});

Maybe it's not possible to mock a promisfyed object at all?

Anybody out there with experiences in this area?

Upvotes: 0

Views: 2384

Answers (1)

Andrew Eddie
Andrew Eddie

Reputation: 988

DBRepository is hard to test because there is a little too much going on - to make testing easier, you need to separate some concerns. At the very least you need to break your business logic (the raw SQL queries) into their own class, something like this:

class GoodsService {
  /**
   * Constructor - inject the database connection into the service.
   *
   * @param {object} db - A db connection
   */
  constructor(db) {
    this.db = db;
  }

  getGoods(queryParams) {
    if (queryParams === undefined) {
      queryParams = {};
    }
    if (queryParams.rowCount === undefined) {
      queryParams.rowCount = 15;
    }

    let query = "SELECT id, name FROM my_table";
    debug('query: ' + query);

    return this.db.queryAsync(query);
  }
}

So now you've separated your business logic from setting up the database connector. You can just pass in a fully instantiated database connection, or a stub into your service class tests like so:

let assert = require('assert');

describe('GoodsService', () => {
  it('should return an array', () => {
    let stubbedDb = {
      queryAsync: () => {
        return Promise.resolve([]);
      }
    };
    let instance = new GoodsService(stubbedDb);

    return instance.getGoods()
      .then((result) => {
        assert(Array.isArray(result), 'should return an array of something');
      });
  });
});

That's somewhat oversimplified but you should get the idea. However there are some things to note.

You don't need fancy things like Chai to test promises. Mocha has good built-in support for that already.

You don't need to use magic like sinon.mock. Instead, keep things simple and just "stub" the methods you need to test in a dependency. However, you could use a "spy" to check the correct SQL is being generated, but I'd do that in an integration test.

Does that help?

Upvotes: 2

Related Questions