你如何在 Node.js 中模拟 MySQL(没有 ORM)?

Posted

技术标签:

【中文标题】你如何在 Node.js 中模拟 MySQL(没有 ORM)?【英文标题】:How do you mock MySQL (without an ORM) in Node.js? 【发布时间】:2012-01-13 10:04:28 【问题描述】:

我正在使用 Node.js 和 felixge 的 node-mysql 客户端。我没有使用 ORM。

我正在使用 Vows 进行测试,并希望能够模拟我的数据库,可能使用 Sinon。由于我本身并没有真正的 DAL(除了node-mysql),我不确定如何去做。我的模型大多是带有很多吸气剂的简单 CRUD。

关于如何实现这一点的任何想法?

【问题讨论】:

你能给我更多的细节吗?例如粘贴一些代码并显示您想要实现的伪代码?我想帮忙。 @alessioalex 老实说,我真的不知道从哪里开始。真的很理想,我想看看别人的模型类和他们相关的模拟/测试。 有人使用 Jest 遇到同样的问题吗? 【参考方案1】:

由于使用 mysql 驱动程序需要您首先创建一个连接,并使用返回的连接控制器的 api - 您需要一个两步的方法。

有两种方法可以做到这一点。

存根createConnection,并让它返回一个存根连接

在设置过程中:

const sinon = require('sinon');
const mysql = require('mysql');
const createConnection = mysql;
let mockConnection;
sinon.stub(mysql, 'createConnection').callsFake((...args) => 
    mockConnection = sinon.stub(createConnection.apply(mysql, args))
      .expects('query').withArgs(.... )//program it how you like :)
    return mockConnection;
)

const mockConnectionFactory = 
  sinon.stub(mysql)
  .expects('createConnection')

拆解期间:

mysql.createConnection.restore();

注意这里query方法是在实例上模拟的,对底层机制没有影响,所以只有createConnection必须恢复。

在连接原型上存根 .query 方法

这种技术有点棘手,因为mysql 驱动程序没有正式公开它的导入连接。 (你可以只导入实现连接的模块,但不能保证任何重构都不会从那里移动它)。 所以为了获得对原型的引用——我通常会创建一个连接并向上遍历构造函数-原型链:

我通常会在一行中完成,但我将其分解为步骤并在此处进行解释:

在设置过程中:

const realConnection = mysql.createConnection()
const mockTarget = realConnection.constructor.prototype;
//Then - brutally
consdt mock = sinon.mock(mockTarget).expect('query'....
//OR - as I prefer the surgical manner
sinon.stub(mockTarget, 'query').expect('query'....

拆解期间

//brutal
mock.restore()
// - OR - surgical:
mockTarget.query.restore()

请注意,我们不会在此处模拟 createConnection 方法。所有连接参数验证仍然会发生(我希望它们发生。我渴望使用最大的真实部件 - 因此模拟获得快速测试所需的绝对最小值)。但是 - query 在原型上被模拟,必须恢复。

另请注意,如果您进行外科手术,verify 将位于模拟方法上,而不是模拟目标上。

这里有一个很好的资源:@​​987654321@

【讨论】:

【参考方案2】:

我最终从@kgilpin 的回答开始,并以这样的方式在 AWS Lambda 中测试 Mysql:

const sinon = require('sinon');
const LambdaTester = require('lambda-tester');
const myLambdaHandler = require( '../../lambdas/myLambda' ).handler;
const mockMysql = sinon.mock(require('mysql'));
const chai = require('chai');
const expect = chai.expect;

describe('Database Write Requests', function() 

 beforeEach(() => 
   mockMysql.expects('createConnection').returns(
     connect: () => 
       console.log('Succesfully connected');
     ,
     query: (query, vars, callback) => 
       callback(null, succesfulDbInsert);
     ,
     end: () => 
       console.log('Connection ended');
     
   );

 );
 after(() => 
   mockMysql.restore();
 );

 describe( 'A call to write to the Database with correct schema', function() 

   it( 'results in a write success', function() 

     return LambdaTester(myLambdaHandler)
       .event(anObject)
       .expectResult((result) => 
         expect(result).to.equal(succesfulDbInsert);
       );
   );
 );


 describe( 'database errors', function() 

   before(() => 
     mockMysql.expects('createConnection').returns(
       connect: () => 
         console.log('Succesfully connected');
       ,
       query: (query, vars, callback) => 
         callback('Database error!', null);
       ,
       end: () => 
         console.log('Connection ended');
       
     );
   );

   after(() => 
     mockMysql.restore();
   );

   it( 'results in a callback error response', function() 


     return LambdaTester(myLambdaHandler)
       .event(anObject)
       .expectError((err) => 
         expect(err.message).to.equal('Something went wrong');
       );
   );
 );
);

我不想要任何实际的数据库连接,所以我手动模拟了所有 mysql 响应。 通过向.returns 添加另一个函数,您可以模拟createConnection 之外的任何方法。

【讨论】:

干得好 - 但是 - 你可以再迈出一步:你很好地模拟了静态 createConnection api,但是你返回了一个手动创建的连接实例,并且失去了 sinon 的力量。我将保存对原始 createConnection 的引用,将其存根,使用 stub#callsFake 对其进行编程,然后在伪造中生成一个连接并将其存根,如 kgilpin 的示例所示。 毫米。我看还不够清楚。我只是说服自己写一个完整的答案......【参考方案3】:

您可以使用 horaa 模拟外部依赖项

而且我也相信 felixge 的节点sandboxed-module 也可以做类似的事情。

所以使用 kgilpin 的相同上下文,在 horaa 中它看起来像:

var mock = horaa('mysql');
mock.hijack('query', function(queryString, queryParam) 
    // do your fake db query (e.g., return fake expected data)
);

//SUT calls and asserts

mock.restore('query');

【讨论】:

sinon.js 似乎是这些天的事实【参考方案4】:

使用 sinon,您可以在整个模块周围放置一个模拟或存根。例如,假设mysql模块有一个函数query

var mock;

mock = sinon.mock(require('mysql'))
mock.expects('query').with(queryString, queryParams).yields(null, rows);

queryString, queryParams 是您期望的输入。 rows 是您期望的输出。

当你的被测类现在需要mysql并调用query方法时,会被sinon拦截验证。

在你的测试期望部分你应该有:

mock.verify()

在您的拆解过程中,您应该将 mysql 恢复到正常功能:

mock.restore()

【讨论】:

.with() 似乎已被弃用,取而代之的是 .withArgs()。我正在测试 sinon 1.7.2 诗乃效果很好。谢谢你。 (不再使用真正的数据库进行单元测试!):)【参考方案5】:

将您的数据库抽象为使用 mysql 的自己的类可能是个好主意。然后,您可以将该类的实例传递给模型的构造函数,而不是使用 require() 加载它。

通过此设置,您可以将模拟数据库实例传递给单元测试文件中的模型。

这是一个小例子:

// db.js
var Db = function() 
   this.driver = require('mysql');
;
Db.prototype.query = function(sql, callback) 
   this.driver... callback (err, results);

module.exports = Db;

// someModel.js
var SomeModel = function (params) 
   this.db = params.db

SomeModel.prototype.getSomeTable (params) 
   var sql = ....
   this.db.query (sql, function ( err, res ) ...

module.exports = SomeModel;

// in app.js
var db = new (require('./db.js'))();
var someModel = new SomeModel (db:db);
var otherModel = new OtherModel (db:db)

// in app.test.js
var db = 
   query: function (sql, callback)  ... callback (...) 

var someModel = new SomeModel (db:db);

【讨论】:

【参考方案6】:

我对 node.js 并不完全熟悉,但是在传统的编程意义上,要实现这样的测试,您需要从数据访问方法中抽象出来。你不能像这样创建一个 DAL 类:

var DataContainer = function () 


DataContainer.prototype.getAllBooks = function() 
    // call mysql api select methods and return results...

现在在测试上下文中,在初始化期间修补您的 getAllBooks 类,例如:

DataContainer.prototype.getAllBooks = function() 
    // Here is where you'd return your mock data in whatever format is expected.
    return [];

调用测试代码时,getAllBooks 将被替换为返回模拟数据的版本,而不是实际调用 mysql。同样,这是一个粗略的概述,因为我对 node.js 并不完全熟悉

【讨论】:

以上是关于你如何在 Node.js 中模拟 MySQL(没有 ORM)?的主要内容,如果未能解决你的问题,请参考以下文章

Node.js mysql 读取 ECONNRESET

Node.js 与 Typescript 和 Mysql 测试路由模拟服务

如何模拟 Node.js child_process spawn 函数?

Node.js-Koa2框架生态实战-从零模拟新浪微博 完整教程

在 node.js mysql 查询中,检查是不是没有找到匹配项

MySQL 与 Node.js