I have a Connection class used to create a Mysql connection pool and execute a transaction. The class has method transaction
that accepts a callback function as a parameter. The callback passed to the transaction
function will be mysql2.query
Inserts.
Connection class:
const mysql2 = require('mysql2/promise');
class Connection {
constructor(options = {}) {
this.options = options;
}
createPool () {
this.pool = mysql2.createPool({
host: this.options.host,
user: this.options.user,
database: 'my_db',
ssl: 'Amazon RDS',
password: this.options.password,
authPlugins: {
mysql_clear_password: () => () => Buffer.from(this.options.password + '\0')
}
});
}
async transaction(callback) {
const connection = await this.pool.getConnection();
await connection.beginTransaction();
try {
await callback(connection);
await connection.commit();
} catch (err) {
await connection.rollback();
throw err;
} finally {
connection.release();
}
}
}
module.exports = { Connection };
Here is how the transaction
function is used.
await conn.transaction(async connection => {
await connection.query(sql1,[values1]);
await connection.query(sql2,[values2]);
await connection.query(sql3,[values3]);
});
My goal is to mock the async transaction
method, but I'm having a difficult time with the callback. Here is what I have tried based on some answers to similar posts and reading the sinon documentation.
https://sinonjs.org/releases/v9.2.0/stubs/
it('should test transaction function in Connection', async () => {
jest.setTimeout(30000);
const results = { affectedRows: 1 };
const poolStub = {
getConnection: sinon.stub().returnsThis(),
query: sinon.stub().returns(results),
beginTransaction: sinon.stub().returnsThis(),
release: sinon.stub(),
};
const createPoolStub = sinon.stub(mysql2, 'createPool').returns(poolStub);
const conn = new conns.Connection();
await conn.createPool();
const actual = await conn.transaction('select 1 + 1 as solution',[]);
expect(actual).to.be.eql(1);
sinon.assert.calledOnce(createPoolStub);
sinon.assert.calledOnce(poolStub.getConnection);
sinon.assert.calledWithExactly(poolStub.query, 'select 1 + 1 as solution');
sinon.assert.calledOnce(poolStub.release);
});
Notice that I am trying to return results = { affectedRows: 1 };
when the query
method is called. However this is not working correctly and the query attempt simply times out.
: Timeout - Async callback was not invoked within the 30000ms timeout specified by jest.setTimeout.
As requested, here is the handler code which shows how the Connection class is used, including how it is imported and initialized
const utils = require('./utils');
const conns = require('./connection');
let response = {
statusCode: 200,
body: {
message: 'SQS event processed.',
},
};
exports.handler = async(event) => {
try {
const values1 = [];
const values2= [];
const values3 = [];
for (const currentMessage of event.Records) {
const data = JSON.parse(currentMessage.body);
console.log(`Processing Received data`);
const {record} = data;
if (record.id == 100) {
values1.push([record.field1,record.field2, record.field3, Date.now(), Date.now(), 'service-user', 'service-user']);
}
if (record.id == 200) {
values2.push([record.field1,record.field2, record.field3, Date.now(), Date.now(), 'service-user', 'service-user']);
}
if (record.id == 300) {
values3.push([record.field1,record.field2, record.field3, Date.now(), Date.now(), 'service-user', 'service-user']);
}
}
const options = {
host: 'my-host',
user: 'service-user'
};
const token = utils.getToken(options);
options.password = token;
const conn = new conns.Connection(options);
conn.createPool();
const sql1 = 'INSERT INTO table1(field1, field2, field4, created_date, modified_date, created_by, modified_by) VALUES ?';
const sql2 = 'INSERT INTO table2(field1, field2, field4, created_date, modified_date, created_by, modified_by) VALUES ?';
const sql3 = 'INSERT INTO table3(field1, field2, field4, created_date, modified_date, created_by, modified_by) VALUES ?';
await conn.transaction(async connection => {
await connection.query(sql1,[values1]);
await connection.query(sql2,[values2]);
await connection.query(sql3,[values3]);
});
await conn.pool.end();
console.log("Connection ended")
} catch (e) {
console.log('There was an error while processing', { errorMessage: e});
response = {
statusCode: 400,
body: e
}
}
return response;
};
Here is unit test solution:
connection.test.js
:
const conns = require('./connection');
const sinon = require('sinon');
const mysql2 = require('mysql2/promise');
describe('64255673', () => {
it('should test transaction function in Connection', async () => {
const results = { affectedRows: 1 };
const connectionStub = {
beginTransaction: sinon.stub(),
commit: sinon.stub(),
rollback: sinon.stub(),
release: sinon.stub(),
};
const poolStub = {
getConnection: sinon.stub().returns(connectionStub),
query: sinon.stub().returns(results),
};
const createPoolStub = sinon.stub(mysql2, 'createPool').returns(poolStub);
const conn = new conns.Connection();
conn.createPool();
const callback = sinon.stub();
await conn.transaction(callback);
sinon.assert.calledOnce(createPoolStub);
sinon.assert.calledOnce(poolStub.getConnection);
sinon.assert.calledOnce(connectionStub.beginTransaction);
sinon.assert.calledOnceWithExactly(callback, connectionStub);
sinon.assert.calledOnce(connectionStub.commit);
sinon.assert.calledOnce(connectionStub.release);
});
});
unit test result with coverage report:
64255673
✓ should test transaction function in Connection
1 passing (13ms)
---------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
---------------|---------|----------|---------|---------|-------------------
All files | 71.43 | 100 | 60 | 76.92 |
connection.js | 71.43 | 100 | 60 | 76.92 | 16,29-30
---------------|---------|----------|---------|---------|-------------------
You can use stub.rejects(value);
to make await connection.commit()
rejected. Then you can test the code in catch
block.