I have a small wrapper class which adds promises to some mysql functionality.
const mysql = require('mysql');
export default class MySQL {
constructor(host, user, password, database, port = 3306) {
this.conn = mysql.createConnection({
host,
port,
user,
password,
database,
});
}
query(sql, args) {
return new Promise((resolve, reject) => {
// eslint-disable-next-line consistent-return
this.conn.query(sql, args, (err, rows) => {
if (err) {
reject(err);
return;
}
resolve(rows);
});
});
}
close() {
return new Promise((resolve, reject) => {
this.conn.end((err) => {
if (err) {
reject(err);
return;
}
resolve();
});
});
}
}
I am trying to write a unit test for this class but am completely stuck trying to mock this.conn
.
I have tried various mixes of proxyquire, sinon, and both combined. When I use proxyquire in a beforeEach hook:
beforeEach(function () {
createConnectionStub = sinon.stub();
MySQL = proxyquire('../../lib/utils/mysql', {
mysql: {
createConnection: createConnectionStub,
},
}).default;
});
and try to set a stub to the conn object:
it('Returns query results', async function () {
stubDb = new MySQL('host', 'user', 'password', 'database');
stubDb.conn = sinon.stub();
const results = await stubDb.query('SELECT * FROM whatever');
});
I keep getting TypeError: this.conn.query is not a function
what is the best way to setup a mock as the this.conn attributes so I can easily assert method calls against it? Any help would be much appreciated
I am an hour late. :)
But I have coded the example and provide alternative to test, so I continue to post this.
I agree, that you do not need proxyquire at all. I use sinon sandbox, stub and fake in example below.
// @file stackoverflow.js
const sinon = require('sinon');
const { expect } = require('chai');
const mysql = require('mysql');
// Change this to your mysql class definition.
const MySQL = require('./mysql.js');
describe('MySQL', function () {
let sandbox;
before(function () {
sandbox = sinon.createSandbox();
});
after(function () {
sandbox.restore();
});
it('constructor fn', function () {
// Prepare stub.
const stubMysql = sandbox.stub(mysql, 'createConnection');
// This just to make sure whether conn is storing this true value.
stubMysql.returns(true);
const test = new MySQL('host', 'user', 'password', 'database');
// Check whether call mysql.createConnection the right way.
expect(test).to.be.an('object');
expect(test).to.have.property('conn', true);
expect(stubMysql.calledOnce).to.equal(true);
expect(stubMysql.args[0]).to.have.lengthOf(1);
expect(stubMysql.args[0][0]).to.have.property('host', 'host');
expect(stubMysql.args[0][0]).to.have.property('user', 'user');
expect(stubMysql.args[0][0]).to.have.property('password', 'password');
expect(stubMysql.args[0][0]).to.have.property('database', 'database');
expect(stubMysql.args[0][0]).to.have.property('port', 3306);
// Restore stub.
stubMysql.restore();
});
it('query fn', async function () {
let fakeCounter = 0;
// Create fake function.
const fakeMysqlQuery = sinon.fake((arg1, arg2, arg3) => {
// On first response: return fake row.
if (fakeCounter === 0) {
fakeCounter += 1;
arg3(undefined, []);
}
// On second response: return error.
if (fakeCounter > 0) {
arg3(new Error('TESTQUERY'));
}
});
// Prepare stub.
const stubMysql = sandbox.stub(mysql, 'createConnection');
stubMysql.returns({
query: fakeMysqlQuery,
});
const test = new MySQL('host', 'user', 'password', 'database');
expect(test).to.be.an('object');
expect(test).to.have.property('conn');
expect(test.conn).to.respondTo('query');
expect(stubMysql.calledOnce).to.equal(true);
expect(test).to.respondTo('query');
// Test success query.
const results = await test.query('SELECT * FROM whatever');
expect(results).to.be.an('array');
expect(results).to.have.lengthOf(0);
expect(fakeMysqlQuery.calledOnce).to.equal(true);
expect(fakeMysqlQuery.args[0]).to.have.lengthOf(3);
expect(fakeMysqlQuery.args[0][0]).to.equal('SELECT * FROM whatever');
expect(fakeMysqlQuery.args[0][1]).to.be.an('undefined');
expect(fakeMysqlQuery.args[0][2]).to.be.an('function');
expect(fakeCounter).to.equal(1);
// Test rejection.
try {
await test.query('SELECT * FROM blablabla');
expect.fail('should not reach here for mysql query test.');
} catch (error) {
expect(error).to.have.property('message', 'TESTQUERY');
expect(fakeMysqlQuery.calledTwice).to.equal(true);
expect(fakeMysqlQuery.args[1]).to.have.lengthOf(3);
expect(fakeMysqlQuery.args[1][0]).to.equal('SELECT * FROM blablabla');
expect(fakeMysqlQuery.args[1][1]).to.be.an('undefined');
expect(fakeMysqlQuery.args[1][2]).to.be.an('function');
}
// Restore stub.
stubMysql.restore();
});
it('close fn', async function () {
let fakeCounter = 0;
// Create fake function.
const fakeMysqlEnd = sinon.fake((arg1) => {
// On first response: return fake row.
if (fakeCounter === 0) {
fakeCounter += 1;
arg1();
}
// On second response: return error.
if (fakeCounter > 0) {
arg1(new Error('TESTCLOSE'));
}
});
// Prepare stub.
const stubMysql = sandbox.stub(mysql, 'createConnection');
stubMysql.returns({
end: fakeMysqlEnd,
});
const test = new MySQL('host', 'user', 'password', 'database');
expect(test).to.be.an('object');
expect(test).to.have.property('conn');
expect(test.conn).to.respondTo('end');
expect(stubMysql.calledOnce).to.equal(true);
expect(test).to.respondTo('close');
// Test success close.
await test.close();
expect(fakeMysqlEnd.calledOnce).to.equal(true);
expect(fakeMysqlEnd.args[0]).to.have.lengthOf(1);
expect(fakeMysqlEnd.args[0][0]).to.be.an('function');
expect(fakeCounter).to.equal(1);
// Test failed close.
try {
await test.close();
expect.fail('should not reach here for mysql end test.');
} catch (error) {
expect(error).to.have.property('message', 'TESTCLOSE');
expect(fakeMysqlEnd.calledTwice).to.equal(true);
expect(fakeMysqlEnd.args[1]).to.have.lengthOf(1);
expect(fakeMysqlEnd.args[1][0]).to.be.an('function');
}
// Restore stub.
stubMysql.restore();
});
});
$ npx mocha stackoverflow.js
MySQL
✓ constructor fn
✓ query fn
✓ close fn
3 passing (21ms)
$
Hope this helps.