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

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

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

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

【问题讨论】:

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

标签: mysql node.js mocking vows sinon


【解决方案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 并不完全熟悉

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多