Sinon имитирует функцию, которая принимает параметр обратного вызова

#node.js #aws-lambda #amazon-rds #sinon #mysql2

#node.js #aws-lambda #amazon-rds #sinon #mysql2

Вопрос:

У меня есть класс Connection, используемый для создания пула соединений Mysql и выполнения транзакции. Класс имеет метод transaction , который принимает функцию обратного вызова в качестве параметра. Обратный вызов, переданный transaction функции, будет mysql2.query Inserts .

Класс подключения:

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

    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.
  

Как и было запрошено, вот код обработчика, который показывает, как используется класс подключения, в том числе как он импортируется и инициализируется

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

Комментарии:

1. Пожалуйста, предоставьте тестируемый код, в котором используется conn.transaction метод, включая способ Connection импорта и инициализации класса

2. Я хотел бы протестировать сам transaction метод

3. Я отредактировал сообщение, чтобы включить обработчик лямбда, который показывает импорт и инициализацию

Ответ №1:

Вот решение для модульного тестирования:

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

результат модульного теста с отчетом о покрытии:

   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. это отличный ответ для тестирования Connection.transaction функции, но как мне протестировать функцию-обработчик лямбда, которая вызывает Connection.transaction ? await conn.transaction(async connection => {...}