Sinon模拟一个接受回调参数的函数

问题描述

我有一个Connection类,用于创建MysqL连接池和执行事务。该类具有方法transaction,该方法接受回调函数作为参数。传递给transaction函数的回调将是MysqL2.query插入。

连接类:

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 };

这里是transaction函数的使用方式。

   await conn.transaction(async connection => {
      await connection.query(sql1,[values1]);
      await connection.query(sql2,[values2]);
      await connection.query(sql3,[values3]);
    });

我的目标是模拟async transaction方法,但是我在回调方面遇到了困难。这是我根据类似帖子的一些答案并阅读sinon文档而尝试过的。 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);
});

请注意,我在尝试调用results = { affectedRows: 1 };方法时试图返回query。但这不能正常工作,查询尝试只是超时。

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

根据要求,以下是处理程序代码,该代码显示如何使用Connection类,包括如何导入和初始化

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(),'service-user','service-user']);
      }
      if (record.id == 200) {
        values2.push([record.field1,'service-user']);
      }
      if (record.id == 300) {
        values3.push([record.field1,'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,modified_by) VALUES ?';
    const sql3 = 'INSERT INTO table3(field1,modified_by) VALUES ?';

    await conn.transaction(async connection => {
      await connection.query(sql1,[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;
};

解决方法

这是单元测试解决方案:

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); }); });

  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          
---------------|---------|----------|---------|---------|-------------------

具有覆盖率报告的单元测试结果:

stub.rejects(value);

您可以使用await connection.commit()来拒绝catch。然后,您可以在{{1}}块中测试代码。