如何使用 sinon 和 Mocha 模拟对 mysql 查询 nodeJS 的 Promisify 调用?

Posted

技术标签:

【中文标题】如何使用 sinon 和 Mocha 模拟对 mysql 查询 nodeJS 的 Promisify 调用?【英文标题】:How to mock promisify call on mysql query nodeJS using sinon and Mocha? 【发布时间】:2021-12-10 13:56:58 【问题描述】:

这是我使用 mysql 的代码 -

import * as mysql from 'mysql';
import promisify from 'util';


 const connectionParams:any = 
     /* set as environment variables */
     host: host,
     user: user,
     password: password,
     port: parseInt(port)
 ;
 var connection:any;
 const getRecords = async (inputValue: string) => 

//validate inputValue

const userIds: string[] = [];
logger.info("Creating mysql connection");
try 
    connection = mysql.createConnection(connectionParams);
    const query = promisify(connection.query).bind(connection);
    const queryResult = await query( sql: sqlQuery, timeout: 1000, values: value1, inputValue] );
    if (queryResult) 
        queryResult.forEach((row) => 
            userIds.push(row.userid);
        );
    
 catch (error) 
    logger.info(error);
    // console.log(error);
    throw new Error('Could not retrieve user IDs');
 finally 
    connection.end();

return userIds;
 ;

这是我的测试 -

it('should return a list of records when right inputs are given', async() => 
        sinon.stub(process, 'env').value(
            'database': 'TESTDB'
        );
        let dummyArray = [ userid: 'xyz' ];
        let createConnection = 
            connect: function(connectionParams: any) 
                return Promise.resolve()
            ,
            query : sinon.stub().withArgs().callsFake(function (...args): Promise<Object>
                const dummyArray = [ userid: 'xyz' ];
                return new Promise(function(resolve)resolve(dummyArray));
            ),
            end: function() 
        ;
        let mySqlStub = 
            createConnection: sinon.stub().returns(createConnection)
        ;
        const dbops = proxyquire('../../lib/dbops', 'mysql': mySqlStub).default;
        expect(await dbops.getUserIds('Delete')).to.deep.equal(['xyz']);
    );

如何为查询编写假函数?

查询: sinon.stub().withArgs().callsFake(function (...args): 承诺 const dummyArray = [ userid: 'xyz' ]; return new Promise(function(resolve)resolve(dummyArray)); )

这对我不起作用。我怎样才能让它工作?我无法让存根函数解析并返回主函数中的预期值。查询只是挂起并在超时后引发错误。错误发生在存根中的“matchingfakes”方法中。

【问题讨论】:

【参考方案1】:

proxyquire 用于对模块或包中的独立函数导出进行存根。由于mysql 是一个对象,您可以通过sinon.stub(obj, 'method') 存根其方法。你不需要使用 proxyquire 包。

即使你使用util.promisify 为NodeJS error-First 回调方法生成promise 版本(mysql.query(sql, callback),回调签名是function (error, results, ...args): void)。你需要使用.callsFake()为这个方法创建一个mock实现,并通过调用它的回调来触发promise版本。

而且,您应该在对环境变量进行存根后import 函数。因为当你导入./dbops模块时,模块作用域内的代码会立即执行,此时环境变量没有被存根。

例如

dbops.ts:

import mysql from 'mysql';
import  promisify  from 'util';

const connectionParams: any = 
  host: process.env.HOST,
  user: process.env.USER,
  password: process.env.PASSWORD,
  port: parseInt(process.env.PORT || '3306'),
;
var connection: any;

const getRecords = async (inputValue: string) => 
  const sqlQuery = 'SELECT * FROM tests';
  const value1 = '';
  const userIds: string[] = [];
  console.info('Creating mysql connection');
  try 
    connection = mysql.createConnection(connectionParams);
    const query = promisify(connection.query).bind(connection);
    const queryResult = await query( sql: sqlQuery, timeout: 1000, values: value1, inputValue );
    if (queryResult) 
      queryResult.forEach((row) => 
        userIds.push(row.userid);
      );
    
   catch (error) 
    console.info(error);
    throw new Error('Could not retrieve user IDs');
   finally 
    connection.end();
  
  return userIds;
;

export  getRecords ;

dbops.test.ts:

import sinon from 'sinon';
import mysql from 'mysql';

describe('69702002', () => 
  it('should return a list of records when right inputs are given', async () => 
    sinon.stub(process, 'env').value(
      HOST: '127.0.0.1',
      USER: 'testuser',
      PASSWORD: 'testpwd',
      PORT: '3306',
    );
    const  getRecords  = await import('./dbops');
    const dummyArray = [ userid: 'xyz' ];

    let connectionStub = 
      query: sinon.stub().callsFake((sql, callback) => 
        callback(null, dummyArray);
      ),
      end: sinon.stub(),
    ;
    sinon.stub(mysql, 'createConnection').returns(connectionStub);
    const actual = await getRecords('test input');
    sinon.assert.match(actual, ['xyz']);
    sinon.assert.calledWithExactly(mysql.createConnection, 
      host: '127.0.0.1',
      user: 'testuser',
      password: 'testpwd',
      port: 3306,
    );
    sinon.assert.calledOnce(connectionStub.end);
  );
);

测试结果:

 69702002
Creating mysql connection
    ✓ should return a list of records when right inputs are given (945ms)


  1 passing (952ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |   90.48 |       50 |     100 |      90 |                   
 dbops.ts |   90.48 |       50 |     100 |      90 | 27-28             
----------|---------|----------|---------|---------|-------------------

【讨论】:

感谢您的回复。由于工作原因没有立即回复,我深表歉意。我尝试了上述解决方案并进行了小修复。如果方法 getRecords 不是默认导出,它会起作用。就我而言,我必须将其设为默认导出。我尝试调整,但它对我不起作用。但是,如果导出不是默认的,这是一个可行的解决方案。由于项目限制,我不得不使用 mysql 无服务器库,为此我能够像为 AWS RDS 建议的解决方案一样进行存根。

以上是关于如何使用 sinon 和 Mocha 模拟对 mysql 查询 nodeJS 的 Promisify 调用?的主要内容,如果未能解决你的问题,请参考以下文章

轻松清理 sinon 存根

使用javascript与Mocha和Sinon进行单元测试问题

跟踪使用 Sinon/Mocha 调用方法的次数

Mocha/Sinon 测试猫鼬里面的快递

markdown 节点单元测试备忘单:Mocha,Chai和Sinon

markdown Mocha,Chai和Sinon的终极单元测试作弊表