简体   繁体   English

Supertest,Mocha和Sinon的单元测试超时

[英]Unit testing with Supertest, Mocha & Sinon timing out

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. 因为它不仅是一个GET,而且这些测试需要扩展到POST,PUT和DELETE。

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 到目前为止,我拥有的代码运行良好,实际上我可以从数据库中获取数据,但是一旦我尝试将负责对数据库进行调用的函数存根,Mocha就会超时

1 failing 1个失败

1) /account_types GET 200 List: Error: timeout of 2000ms exceeded. 1)/ account_types GET 200列表:错误:超时超过2000ms。 Ensure the done() callback is being called in this test. 确保此测试中调用了done()回调。 at null. 为空。 (C:\\Code\\JS\\general_admin_service\\node_modules\\mocha\\lib\\runnable.js:215:19) (C:\\ Code \\ JS \\ general_admin_service \\ node_modules \\ mocha \\ lib \\ runnable.js:215:19)

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. 我知道由于代码卡在某处而未调用done()回调,但是我不明白自己在做什么错。

I used the following references to get where I am: 我使用以下参考资料来了解自己的位置:

My code is as follows: 我的代码如下:

The Test: 考试:

'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();
            });
    });

});

Restify endpoint: 重新调整端点:

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();
}

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); 我期望发生的事情是,因为我不加入query_list函数,所以我应该输入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. 在期望值到位之后,我应该看到[{id: "test"}] ,但是显然还没有到此为止。

What am I doing wrong? 我究竟做错了什么?

UPDATE: Added the full select.js file. 更新:添加了完整的select.js文件。

As you already make clear in the comments, it's difficult to test code that's deeply nested. 正如您已经在注释中明确指出的那样,很难测试深度嵌套的代码。

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. 通常,使用回调或Promise更好,这样应用程序的每一部分都可以处理它负责的部分,但不能(更多)处理。 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. 显然可以调用其他函数,例如执行数据库查询的函数,但是与其让这些函数发送回响应,不如使用回调将查询结果“回调”到路由处理程序。

Something like this: 像这样:

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();
  });
});

In terms of using Sinon to test something like this: it really depends on the exact implementation. 在使用Sinon测试类似的东西方面:它实际上取决于确切的实现。 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. 我可以提供一个简单的示例,说明如何对select.query_list的上述用法进行select.query_list ,以确保响应包含正确的数据。

The basic stub looks like this: 基本存根如下所示:

sinon.stub(select, 'query_list').yieldsAsync(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' } ] . 这样做是在select.query_list()被调用时,它将调用接收到的第一个回调参数(通过检查每个参数以查看哪个是函数来实现),其参数为null, [ { id : 'test' } ]

Those are the err and records arguments of the callback function passed in the handler. 这些是errrecords在处理程序中传递的回调函数的参数。 So you can use this to skip the database query entirely and pretend that the query yielded a particular array of records. 因此,您可以使用它完全跳过数据库查询,并假装该查询产生了特定的记录数组。

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. 从那里开始,调用res.send() (这是您最初遇到的问题:根本没有被调用,因为它是在应用程序的一部分中执行的,而由于存根而没有被调用) ),然后您可以检查测试结果是否符合预期。

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). 如果您想在调用堆栈中更深地存根某个函数,会变得有些复杂,但是使用正确的Sinon工具(例如.yields*或使用间谍代替存根),通常并不困难(只要所有函数您想存根/间谍是可访问的(即已导出)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM