Sinon.stub() return 每次调用的值都不一样

Sinon.stub() return different values every time it's called

这是我正在为其编写测试的代码:

'use strict';

var internals = {};

var _ = require('lodash');

module.exports = {
    initialize: function (query) {
        internals.query = query;
    },

    createField: function (fieldId, accountId, payload) {

        function callQuery (parList) {
            var query = 'INSERT into fields VALUES (:uuid, :accountId, :shortcutName, :displayName, :fieldType, :widgetType, :columnOrder, :options, :required, NULL)';
            return internals.query(query, parList, function () { return fieldId; });
        }

        var increment = 10;
        var parameterList = {
            'uuid': fieldId,
            'accountId': accountId,
            'shortcutName': payload.shortcutName,
            'displayName': payload.displayName,
            'fieldType': payload.fieldType,
            'widgetType': payload.widgetType,
            'columnOrder': payload.columnOrder,
            'options': JSON.stringify(payload.options) || null,
            'required': payload.required || 'f'
        };
        if (!payload.columnOrder) {
            var columnQuery = 'SELECT MAX(column_order) from fields';
            return internals.query(columnQuery, {}, function (x) {return x; })
                .then(function (results) {
                    var highestColumnOrder = results[0]['MAX(column_order)'];
                    var newHighestColumnOrder = Math.ceil(highestColumnOrder / 10) * 10;
                    if (newHighestColumnOrder > highestColumnOrder) {
                        parameterList.columnOrder = newHighestColumnOrder;
                    } else {
                        parameterList.columnOrder = newHighestColumnOrder + increment;
                    }
                    return callQuery(parameterList);
                });
        } else {
            return callQuery(parameterList);
        }
    },

    getFieldsByAccountId: function(accountId, showDeleted) {
        var callQuery = function(paramList) {
            var query = 'SELECT ' + paramList.columns.join(", ") + ' FROM fields WHERE account_id = :account_id';

            if (!showDeleted) {
                query +=  ' AND archived_at IS NULL';
            }

            return internals.query(query, paramList, function(rows) {
                return _.each(rows, function(row) {
                    if(row.options) {
                        row.options = JSON.parse(row.options);
                    }
                    row.required = !!row.required;
                });
            });
        };

        var columnList = ["uuid", "account_id", "shortcut_name", "display_name", "field_type", "required", "column_order", "options"];
        var paramList = {'account_id': accountId};

        if (showDeleted) {
            columnList.push("archived_at");
        }

        _.extend(paramList, {'columns': columnList});

        return callQuery(paramList);
    }
};

这是我的测试:

        'use strict';

var assert = require('assert');
var sinon = require('sinon');
var Promise = require('bluebird');
var proxyquire = require('proxyquire');

var returnedValues = require('../../../return_values.js');
var fieldGateway = proxyquire('../../../../src/fields/lib/gateway', {});

describe('gateway', function () {
    var accountId = 100;
    var fieldId = 200;
    var _query, sql, mockData, rows;

    describe('createField', function() {
        describe('is successful with a column order value', function () {

            beforeEach(function() {
                sql = 'INSERT into fields VALUES (:uuid, :accountId, :shortcutName, :displayName, :fieldType, :widgetType, :columnOrder, :options, :required, NULL)';
                mockData = returnedValues.getFieldInputValues();
            });

            it("should only insert new field", function () {
                _query = sinon.spy(function() { return Promise.resolve(); });
                fieldGateway.initialize(_query);
                fieldGateway.createField(fieldId, accountId, mockData);

                mockData.accountId = accountId;
                mockData.uuid = fieldId;
                mockData.options = JSON.stringify(mockData.options);
                assert.equal(sql, _query.getCall(0).args[0]);
                assert.deepEqual(mockData, _query.getCall(0).args[1]);
            });

            it.only("_query should be called with the right sql statement and parameterList", function () {
                _query = sinon.stub().returns(Promise.resolve(fieldId));
                // _query.onCall(0).returns(Promise.resolve([{'MAX(column_order)': 10}]));
                // _query.onCall(1).returns(Promise.resolve(fieldId));
                fieldGateway.initialize(_query);
                delete mockData.columnOrder;

                fieldGateway.createField(fieldId, accountId, mockData);
                console.log(_query.args);
                assert.equal(sql, _query.getCall(0).args[0]);

                fieldGateway.createField.restore();
            });
        });
    });

});

问题是当测试运行时,唯一运行的 SQL 查询是 SELECT 语句。应该发生的是一个 SQL 语句运行,然后一个 INSERT 语句运行

发生这种情况是因为 bluebird 是一个真正的 Promise/A+ 兼容库。根据定义,所有链式承诺都必须 运行 在不同的执行周期中。所以只有第一个承诺是同步执行的(在同一个滴答中)。

你应该告诉 mocha "wait" 让其他人行动。您可以通过在单元测试中指定 done 回调并在您的承诺完成工作时相应地调用它来实现

 it.only("_query should be called with the right sql statement and parameterList", function (done) {
    _query = sinon.stub().returns(Promise.resolve(fieldId));
    fieldGateway.initialize(_query);
    delete mockData.columnOrder;
    fieldGateway.createField(fieldId, accountId, mockData)
    .then(function(){
           /// assertion code should be adjusted here
         console.log(_query.args);
         assert.equal(sql, _query.getCall(0).args[0]);
         fieldGateway.createField.restore();
         //tell Mocha we're done, it can stop waiting
         done();
    })
    .catch(function(error) { 
      //in case promise chain was rejected unexpectedly
      //gracefully fail the test
         done(error); 
    };
});

无论何时测试返回承诺的函数,都应始终在 .then

中处理结果