带有回调的 Sinon 测试函数调用
Sinon test function call with a callback
我有一个连接 class,带有用于执行 mysql 交易的交易功能。它采用回调作为参数,表示要执行的任何 mysql 查询。在 Whosebug 用户的帮助下,我为函数本身创建了一个单元测试,但我在测试实际使用该函数的 lambda 处理程序时遇到了问题。
这是连接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 + '[=10=]')
}
});
}
async transaction(callback) {
const connection = await this.pool.getConnection();
await connection.beginTransaction();
try {
await callback(connection);
await connection.commit();
} catch (err) {
await connection.rollback();
console.log("An exception was thrown and the mysql transaction has been rolled back", err);
} finally {
connection.release();
}
}
}
module.exports = { Connection };
这是 lambda 处理程序。我只包含了最相关的部分
const conns = require('./connection');
let response = {
statusCode: 200,
body: {
message: 'SQS event processed.',
},
};
exports.handler = async(event) => {
console.log(event.Records);
try {
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]);
});
} catch (e) {
console.log('There was an error while processing', { errorMessage: e});
response = {
statusCode: 400,
body: e
}
}
return response;
};
我在这里尝试对处理程序进行单元测试:
test('Should test handler without mocking', 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);
// attempt to mock transaction function
sinon.stub(conn, 'transaction').returns(results);
const response = await index.handler(mocks.baseMessage, null);
expect(response.statusCode).toBe(200);
});
在我尝试模拟 conn.transaction 调用之前,测试似乎一直有效。它只是给出了一个ReferenceError: conn is not defined
。我怎样才能模拟 transaction
的结果,以便处理函数将被测试覆盖?
单元测试解决方案:
handler.js
:
const conns = require('./connection');
let response = {
statusCode: 200,
body: {
message: 'SQS event processed.',
},
};
exports.handler = async (event) => {
console.log(event.Records);
const options = {};
const [values1, values2, values3] = [1, 2, 3];
try {
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]);
});
} catch (e) {
console.log('There was an error while processing', { errorMessage: e });
response = {
statusCode: 400,
body: e,
};
}
return response;
};
handler.test.js
:
const index = require('./handler');
const conns = require('./connection');
const { expect } = require('chai');
const sinon = require('sinon');
const mocks = {
baseMessage: {
Records: [],
},
};
describe('64310254', () => {
it('Should test handler without mocking', async () => {
const poolConnectionStub = {
query: sinon.stub(),
};
const connectionStub = {
createPool: sinon.stub(),
transaction: sinon.stub().callsFake(async (callback) => {
await callback(poolConnectionStub);
}),
};
const ConnectionStub = sinon.stub(conns, 'Connection').returns(connectionStub);
const response = await index.handler(mocks.baseMessage, null);
expect(response.statusCode).to.be.eq(200);
sinon.assert.calledOnceWithExactly(ConnectionStub, {});
sinon.assert.calledOnce(connectionStub.createPool);
sinon.assert.calledOnceWithExactly(connectionStub.transaction, sinon.match.func);
sinon.assert.calledThrice(poolConnectionStub.query);
ConnectionStub.restore();
});
it('should handle error', async () => {
const poolConnectionStub = {
query: sinon.stub(),
};
const connectionStub = {
createPool: sinon.stub(),
transaction: sinon.stub().rejects(new Error('timeout')),
};
const ConnectionStub = sinon.stub(conns, 'Connection').returns(connectionStub);
const response = await index.handler(mocks.baseMessage, null);
expect(response.statusCode).to.be.eq(400);
sinon.assert.calledOnceWithExactly(ConnectionStub, {});
sinon.assert.calledOnce(connectionStub.createPool);
sinon.assert.calledOnceWithExactly(connectionStub.transaction, sinon.match.func);
sinon.assert.notCalled(poolConnectionStub.query);
ConnectionStub.restore();
});
});
带有覆盖率报告的单元测试结果:
64310254
[]
✓ Should test handler without mocking
[]
There was an error while processing { errorMessage:
Error: timeout
at Context.it (/Users/ldu020/workspace/github.com/mrdulin/expressjs-research/src/Whosebug/64310254/handler.test.js:40:41)
at callFn (/Users/ldu020/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runnable.js:387:21)
at Test.Runnable.run (/Users/ldu020/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runnable.js:379:7)
at Runner.runTest (/Users/ldu020/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:535:10)
at /Users/ldu020/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:653:12
at next (/Users/ldu020/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:447:14)
at /Users/ldu020/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:457:7
at next (/Users/ldu020/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:362:14)
at Immediate.<anonymous> (/Users/ldu020/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:425:5)
at runCallback (timers.js:705:18)
at tryOnImmediate (timers.js:676:5)
at processImmediate (timers.js:658:5) }
✓ should handle error
2 passing (42ms)
---------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
---------------|---------|----------|---------|---------|-------------------
All files | 63.64 | 0 | 28.57 | 65.63 |
connection.js | 14.29 | 0 | 0 | 15.38 | 5-32
handler.js | 100 | 100 | 100 | 100 |
---------------|---------|----------|---------|---------|-------------------
我有一个连接 class,带有用于执行 mysql 交易的交易功能。它采用回调作为参数,表示要执行的任何 mysql 查询。在 Whosebug 用户的帮助下,我为函数本身创建了一个单元测试,但我在测试实际使用该函数的 lambda 处理程序时遇到了问题。
这是连接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 + '[=10=]')
}
});
}
async transaction(callback) {
const connection = await this.pool.getConnection();
await connection.beginTransaction();
try {
await callback(connection);
await connection.commit();
} catch (err) {
await connection.rollback();
console.log("An exception was thrown and the mysql transaction has been rolled back", err);
} finally {
connection.release();
}
}
}
module.exports = { Connection };
这是 lambda 处理程序。我只包含了最相关的部分
const conns = require('./connection');
let response = {
statusCode: 200,
body: {
message: 'SQS event processed.',
},
};
exports.handler = async(event) => {
console.log(event.Records);
try {
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]);
});
} catch (e) {
console.log('There was an error while processing', { errorMessage: e});
response = {
statusCode: 400,
body: e
}
}
return response;
};
我在这里尝试对处理程序进行单元测试:
test('Should test handler without mocking', 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);
// attempt to mock transaction function
sinon.stub(conn, 'transaction').returns(results);
const response = await index.handler(mocks.baseMessage, null);
expect(response.statusCode).toBe(200);
});
在我尝试模拟 conn.transaction 调用之前,测试似乎一直有效。它只是给出了一个ReferenceError: conn is not defined
。我怎样才能模拟 transaction
的结果,以便处理函数将被测试覆盖?
单元测试解决方案:
handler.js
:
const conns = require('./connection');
let response = {
statusCode: 200,
body: {
message: 'SQS event processed.',
},
};
exports.handler = async (event) => {
console.log(event.Records);
const options = {};
const [values1, values2, values3] = [1, 2, 3];
try {
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]);
});
} catch (e) {
console.log('There was an error while processing', { errorMessage: e });
response = {
statusCode: 400,
body: e,
};
}
return response;
};
handler.test.js
:
const index = require('./handler');
const conns = require('./connection');
const { expect } = require('chai');
const sinon = require('sinon');
const mocks = {
baseMessage: {
Records: [],
},
};
describe('64310254', () => {
it('Should test handler without mocking', async () => {
const poolConnectionStub = {
query: sinon.stub(),
};
const connectionStub = {
createPool: sinon.stub(),
transaction: sinon.stub().callsFake(async (callback) => {
await callback(poolConnectionStub);
}),
};
const ConnectionStub = sinon.stub(conns, 'Connection').returns(connectionStub);
const response = await index.handler(mocks.baseMessage, null);
expect(response.statusCode).to.be.eq(200);
sinon.assert.calledOnceWithExactly(ConnectionStub, {});
sinon.assert.calledOnce(connectionStub.createPool);
sinon.assert.calledOnceWithExactly(connectionStub.transaction, sinon.match.func);
sinon.assert.calledThrice(poolConnectionStub.query);
ConnectionStub.restore();
});
it('should handle error', async () => {
const poolConnectionStub = {
query: sinon.stub(),
};
const connectionStub = {
createPool: sinon.stub(),
transaction: sinon.stub().rejects(new Error('timeout')),
};
const ConnectionStub = sinon.stub(conns, 'Connection').returns(connectionStub);
const response = await index.handler(mocks.baseMessage, null);
expect(response.statusCode).to.be.eq(400);
sinon.assert.calledOnceWithExactly(ConnectionStub, {});
sinon.assert.calledOnce(connectionStub.createPool);
sinon.assert.calledOnceWithExactly(connectionStub.transaction, sinon.match.func);
sinon.assert.notCalled(poolConnectionStub.query);
ConnectionStub.restore();
});
});
带有覆盖率报告的单元测试结果:
64310254
[]
✓ Should test handler without mocking
[]
There was an error while processing { errorMessage:
Error: timeout
at Context.it (/Users/ldu020/workspace/github.com/mrdulin/expressjs-research/src/Whosebug/64310254/handler.test.js:40:41)
at callFn (/Users/ldu020/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runnable.js:387:21)
at Test.Runnable.run (/Users/ldu020/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runnable.js:379:7)
at Runner.runTest (/Users/ldu020/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:535:10)
at /Users/ldu020/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:653:12
at next (/Users/ldu020/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:447:14)
at /Users/ldu020/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:457:7
at next (/Users/ldu020/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:362:14)
at Immediate.<anonymous> (/Users/ldu020/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:425:5)
at runCallback (timers.js:705:18)
at tryOnImmediate (timers.js:676:5)
at processImmediate (timers.js:658:5) }
✓ should handle error
2 passing (42ms)
---------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
---------------|---------|----------|---------|---------|-------------------
All files | 63.64 | 0 | 28.57 | 65.63 |
connection.js | 14.29 | 0 | 0 | 15.38 | 5-32
handler.js | 100 | 100 | 100 | 100 |
---------------|---------|----------|---------|---------|-------------------