我正在使用felixge的node-mysql
客户端和Node.js
。我没有使用ORM。
我正在使用Vows进行测试,希望能够模拟我的数据库,可能使用Sinon。由于我实际上没有一个DAL(除了node-mysql
),所以我不太确定如何做到这一点。我的模型主要是简单的CRUD操作和许多getter。
有什么想法可以实现这个吗?
我正在使用felixge的node-mysql
客户端和Node.js
。我没有使用ORM。
我正在使用Vows进行测试,希望能够模拟我的数据库,可能使用Sinon。由于我实际上没有一个DAL(除了node-mysql
),所以我不太确定如何做到这一点。我的模型主要是简单的CRUD操作和许多getter。
有什么想法可以实现这个吗?
使用 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()
在你的拆除(teardown)函数中,你应该将mysql恢复为正常功能:
mock.restore()
将数据库抽象成一个独立的类,使用 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});
我不是很熟悉node.js,但从传统的编程角度来看,要实现这样的测试,你需要抽象出数据访问方法。你能不能创建一个像这样的DAL类:
var DataContainer = function () {
}
DataContainer.prototype.getAllBooks = function() {
// call mysql api select methods and return results...
}
DataContainer.prototype.getAllBooks = function() {
// Here is where you'd return your mock data in whatever format is expected.
return [];
}
我最终采用了 @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
调用的方法。
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');
使用mysql驱动程序需要您首先创建一个连接,然后使用返回的连接控制器的API - 您需要采用两步方法。
有两种方法可以做到这一点。
在设置期间:
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
。
这种技术有点棘手,因为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
将在模拟方法上,而不是在mockTarget上。