47

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

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

关于如何做到这一点的任何想法?

4

6 回答 6

40

使用 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()
于 2012-04-12T13:19:07.837 回答
12

将数据库抽象为使用 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});
于 2012-03-15T15:14:00.527 回答
5

我对 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 并不完全熟悉

于 2012-01-07T14:59:22.477 回答
5

我最终从@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

于 2017-05-31T18:30:29.923 回答
3

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

而且我也相信 felixge 的节点沙盒模块也可以做类似的事情。

因此,使用 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');
于 2012-06-05T21:46:33.067 回答
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则将在模拟方法上,而不是在模拟目标上。

这是一个很好的资源:http ://devdocs.io/sinon~6-stubs/

于 2018-09-13T08:28:09.277 回答