Supertest,Mocha&的单元测试希农超时 [英] Unit testing with Supertest, Mocha & Sinon timing out

查看:68
本文介绍了Supertest,Mocha&的单元测试希农超时的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试编写一个单元/集成测试,我想在其中获取数据库中所有内容的列表.因为这不仅是一个GET,而且这些测试需要扩展到POST,PUT&删除.

I am trying to write a unit/integration test where I want to get a list of things in the database. For not it is only a GET, but these tests needs to extend to POST, PUT & DELETE.

到目前为止,我所拥有的代码运行良好,实际上我可以从数据库中获取数据,但是一旦我尝试终止负责对数据库进行调用的功能,Mocha就会超时

The code I have thus far works fine, I can actually get data from the DB, but as soon as I try to stub out the function which is responsable for making the call to the DB, Mocha times out

1个失败

1 failing

1)/account_types GET 200列表: 错误:超过2000ms的超时时间.确保在此测试中调用了done()回调. 为空. (C:\ Code \ JS \ general_admin_service \ node_modules \ mocha \ lib \ runnable.js:215:19)

1) /account_types GET 200 List: Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test. at null. (C:\Code\JS\general_admin_service\node_modules\mocha\lib\runnable.js:215:19)

我知道由于代码停留在某处而没有调用done()回调,但是,我不明白自己在做什么错.

I understand the done() callback isn't being called because the code is stuck somewhere, however, I do not understand what I am doing wrong.

我使用以下参考资料来了解自己所在的位置:

I used the following references to get where I am:

  • Testing With Mocha, Sinon.js & Mocking Request
  • Lessons learned from unit testing with Sinon.JS

我的代码如下:

测试:

'use strict';

var expect = require('chai').expect,
    request = require('supertest'),
    chance = require('chance').Chance(),
    server = require('../server'),
    sinon = require('sinon'),
    select = require('../../helpers/data_access/select');

describe("/account_types", function () {

    before(function(done){
        sinon
            .stub(select, "query_list")
            .returns([{id: "test"}]);

        done();
    });

    after(function(done){
        select
            .query_list
            .restore();

        done();
    });

    it('GET 200 List', function (done) {

        request(server.baseURL)
            .get('/api/v1/account_types')
            .set('Accept', 'application/json')
            .expect('Content-Type', 'application/json')
            .expect(200)
            .end(function (err, res) {
                /* istanbul ignore if */
                if (err)
                    return done(err);

                expect(res.body).to.include.keys('result');
                expect(res.body.result).to.not.be.null;
                expect(res.body.result).to.not.be.undefined;
                expect(res.body.result).to.be.an('Array');
                expect(res.body.result.length).to.be.above(0);

                //expect(select.query_list).to.have.been.calledOnce;

                return done();
            });
    });

});

重新调整端点:

var select = require('../helpers/data_access/select')

module.exports = function (server) {
        var query = "..."

        return select.query_list(res, next, db_config, query);
    });
};

select.js:

select.js:

var sql = require('mssql');

module.exports = {
    query_list: function (res, next, config, sql_query) {
        return query(res, next, config, sql_query, true);
    },
    query_single: function (res, next, config, sql_query) {
        return query(res, next, config, sql_query, false);
    }
};

function query(res, next, config, sql_query, isList) {
    var connection = new sql.Connection(config);

    connection.connect(function (err) {
        if (err) {
            return on_error(err, res);
        }

        var request = new sql.Request(connection);

        request.query(sql_query, function (err, response) {
            connection.close();

            if (err) {
                return on_error(err, res);
            }

            if (isList) {
                return return_list(res, response, next);
            } else {
                return return_single(res, response, next);
            }
        });
    });
}

function on_error(error, res, next) {
    res.status(500).send(error);
    return next();
}

function return_list(res, response, next) {
    res.send({result: response});
    return next();
}

function return_single(res, response, next) {
    res.send({result: response[0]});
    return next();
}

我希望发生的事情是,因为我对query_list函数进行了存根处理,所以我应该在期望值到位之后放置console.log(res.body.result);,我应该看到[{id: "test"}]的返回值,但是显然不是达到这一点.

What I expect to happen is that because I stub out the query_list function, should I wish to put a console.log(res.body.result); after the expect's I have in place, I should see a return of [{id: "test"}], but it is obviously not getting to that point.

我在做什么错了?

更新:添加了完整的select.js文件.

UPDATE: Added the full select.js file.

推荐答案

正如您在注释中已经明确指出的那样,很难测试深度嵌套的代码.

As you already make clear in the comments, it's difficult to test code that's deeply nested.

使用回调或Promise通常会更好,这样您的应用程序的每个部分都可以处理其负责的部分,但不能(更多)处理.因此,您的路由处理程序将处理请求.显然可以调用其他函数,例如执行数据库查询的函数,但是与其让这些函数发送回响应,不如使用回调将查询结果回调"到路由处理程序.

It's usually much better to work with callbacks or promises, so that each piece of your app will handle the part it's responsible for, but not (much) more. So your route handler will handle the request and the response. It's obviously okay to call other functions, like ones that perform database queries, but instead of letting those functions send back a response, you use callbacks that "call back" to the route handler with the query results.

类似这样的东西:

server.get('/api/v1/account_types', function(req, res, next) {
  select.query_list(QUERY, function(err, records) {
    if (err) return next(err);
    res.send({ results : records });
    next();
  });
});

就使用Sinon测试类似的东西而言:它实际上取决于确切的实现.我可以提供一个简单的示例,说明如何对上述select.query_list的用法进行存根,以确保响应中包含正确的数据.

In terms of using Sinon to test something like this: it really depends on the exact implementation. I can provide a quick example on how to stub the above usage of select.query_list, to make sure that the response contains the correct data.

基本存根如下:

sinon.stub(select, 'query_list').yieldsAsync(null, [ { id : 'test' } ]);

这是什么,当select.query_list()被调用时,它将使用参数null, [ { id : 'test' } ]调用接收到的第一个回调参数(通过检查每个参数以查看哪个是函数来实现).

What this does, is when select.query_list() gets call, it will call the first callback argument it receives (it does this by checking each argument to see which is a function) with the arguments null, [ { id : 'test' } ].

这些是在处理程序中传递的回调函数的errrecords参数.因此,您可以使用它来完全跳过数据库查询,并假装该查询产生了特定的记录数组.

Those are the err and records arguments of the callback function passed in the handler. So you can use this to skip the database query entirely and pretend that the query yielded a particular array of records.

从那里,res.send()被调用(这是您最初遇到的问题:根本没有被调用,因为它是在您的应用程序的一部分中执行的,因为您的应用程序没有被调用存根),然后您可以检查测试结果响应数据是否符合预期.

From there, res.send() gets called (which was the issue that you initially ran into: it didn't get called at all because it was being performed in a part of your app that wasn't getting called because of your stub) and you can check in your test if the resulting response data is as expected.

如果您想在调用堆栈中更深地存根一个函数,会变得有些复杂,但是使用正确的Sinon工具(如.yields*或使用间谍而不是存根)通常并不困难(前提是所有这些您想要存根/间谍的功能是可以访问的(即已导出).

It becomes a bit more complicated if you want to stub a function deeper in the call stack, but with the correct Sinon tools (like .yields*, or using spies instead of stubs) it's usually not terribly difficult (provided that all the functions that you want to stub/spy are accessible, that is, exported).

这篇关于Supertest,Mocha&的单元测试希农超时的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆