你如何在 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 与 Typescript 和 Mysql 测试路由模拟服务
如何模拟 Node.js child_process spawn 函数?
Node.js-Koa2框架生态实战-从零模拟新浪微博 完整教程