Node.js nodejs-测试失败,但正在调用回调

Node.js nodejs-测试失败,但正在调用回调,node.js,unit-testing,mocha.js,sinon,chai,Node.js,Unit Testing,Mocha.js,Sinon,Chai,我有一个导出的模块,它有一个方法editHeroMage,我正试图使用mocha、chai和sinon对其进行测试。模块有两个作为参数传递的对象,连接和查询。这些是mySql对象,一个包含到数据库的连接,另一个包含在各自模块中定义的查询字符串。我正在导出并尝试测试的expObj是一个“助手”模块 我已经成功地测试了此模块的其他方法,测试方法与我尝试测试此方法的方法相同,但是,当我遇到由于某种原因使用async模块的方法时,我的测试不再按预期进行。我想知道在这个特殊的例子中我是否遗漏了一些东西,因

我有一个导出的模块,它有一个方法
editHeroMage
,我正试图使用
mocha
chai
sinon
对其进行测试。模块有两个作为参数传递的对象,
连接
查询
。这些是
mySql
对象,一个包含到数据库的连接,另一个包含在各自模块中定义的查询字符串。我正在导出并尝试测试的
expObj
是一个“助手”模块

我已经成功地测试了此模块的其他方法,测试方法与我尝试测试此方法的方法相同,但是,当我遇到由于某种原因使用
async
模块的方法时,我的测试不再按预期进行。我想知道在这个特殊的例子中我是否遗漏了一些东西,因为我已经测试过同样使用
async
的其他模块和方法,并且没有遇到这种行为

当我运行测试时,它会像预期的那样记录“HELLO!”,但是调用了
callbackSpy
的断言失败

我在这里发疯了!请帮忙!发生了什么事?测试服之间会有污染吗

试验方法:

expObj.editHeroImage = function(connection, queries, postId, postData, callback) {
  async.waterfall([
    function(next) {
      var qString = queries.getSinglePostById();
      connection.query(qString, [postId], function(err, results) {
        if (err) {
          return next(err);
        }
        if (!results.length) {
          console.log('NO POST FOUND WITH ID ' + postId);
          return callback();
        }
        next(null, results[0].hero_image);
      });
    },
    function(heroImageId, next) {
      if (!heroImageId) {
        console.log('HERO IMAGE IS NEW - NEXT TICK!');
        return next();
      }
      // Delete resized images of hero image
      var queryStr = queries.deleteResizedImages();
      var resizedVals = [heroImageId];
      connection.query(queryStr, resizedVals, function(err) {
        if (err) {
          return callback(err);
        }
        console.log('DELETED RESIZED IMAGES OF HERO IMAGE ' + heroImageId);
        var qString = queries.updateHeroImagePath();
        var values = [postData.hero_image, heroImageId];
        return connection.query(qString, values, function(err, results) {
          if (err) {
            return next(err);
          }
          console.log('UPDATED HERO IMAGE ' + heroImageId + ' WITH PATH ' + postData.hero_image);
          next('break');
        });
      });
    },
    function addHeroImage(next) {
      var qString = queries.insertImage();
      var values = [postData.hero_image, postId];
      connection.query(qString, values, function(err, results) {
        if (err) {
          return next(err);
        }
        next(null, results.insertId);
      });
    },
    function addHeroImagePathToPost(heroImageId, next) {
      var qString = queries.saveHeroImageId();
      var values = [heroImageId, postId];
      connection.query(qString, values, function(err) {
        if (err) {
          return next(err);
        }
        next();
      });
    }
  ], function(err) {
    if (err && err !== 'break') {
      return callback(err);
    }
    console.log('HELLO!');
    callback(null);
  });
};
测试,带有设置:

'use strict';

var chai = require('chai');
var sinonChai = require("sinon-chai");
var proxyquire = require('proxyquire');
var sinon = require('sinon');
chai.use(sinonChai);
var expect = chai.expect;

describe('HELPERS', function() {
  var testedModule,
    callbackSpy,
    fakeConnectionObj,
    fakeQueriesObj,
    fakePost,
    fakeSnakeCaseObj,
    queryStub,
    connectionStub,
    manageStub,
    fakeCamelCaseObj;

  beforeEach(function() {
    fakePost = {};
    fakeConnectionObj = {};
    fakeQueriesObj = {
      getPostIdFromImage: function() {},
      insertResizedImages: function() {},
      createPost: function() {},
      getPostImages: function() {},
      getPostsAlternativesImages: function() {},
      getSinglePostById: function() {},
      getAllImages: function() {},
      insertImage: function() {},
      deleteMainImage: function() {},
      deleteResizedImages: function() {},
      updateHeroImagePath: function() {},
      saveHeroImageId: function() {}
    };

    afterEach(function() {
      queryStub.resetBehavior();
    });
    fakeSnakeCaseObj = {
      sub_title: '123',
      hero_image: '456'
    };
    fakeCamelCaseObj = {
      subTitle: '123',
      heroImage: '456'
    };
    callbackSpy = sinon.spy();
    queryStub = sinon.stub();
    manageStub = sinon.stub();
    connectionStub = {query: queryStub};
    testedModule = proxyquire('./../../../../lib/modules/mySql/workers/helpers', {
      './../../../factories/notification-service': {
        select: function() {
          return {manageSns: manageStub};
        }
      }
    });
  });

it('edits hero image', function() {
    var _post = {
      id: '123',
      title: 'vf',
      sub_title: 'vf',
      slug: 'vf',
      reading_time: 4,
      created_at: '123',
      published_at: '123',
      deleted_on: false,
      hero_image: 'hero_image_path'
    };
    var _postId = '123';
    queryStub.onCall(0).callsArgWith(2, null, [{hero_image: '55'}]);
    queryStub.onCall(1).callsArgWith(2, null);
    queryStub.onCall(2).callsArgWith(2, null);
    testedModule.editHeroImage(connectionStub, fakeQueriesObj, _postId, _post, function() {
      console.log(arguments); // --> {'0': null} as expected
      callbackSpy.apply(null, arguments);
    });
    expect(callbackSpy).has.been.calledWith(null);
  });
});

您的断言可能在异步函数返回之前执行

有许多方法可以确保异步函数已完成执行。最干净的方法是以不同的格式设置摩卡测试

describe('...', function () {
    var callbackSpy;

    before(function () {
        var _post = {
            id: '123',
            title: 'vf',
            sub_title: 'vf',
            slug: 'vf',
            reading_time: 4,
            created_at: '123',
            published_at: '123',
            deleted_on: false,
            hero_image: 'hero_image_path'
        };
        var _postId = '123';
        queryStub.onCall(0).callsArgWith(2, null, [{
            hero_image: '55'
        }]);
        queryStub.onCall(1).callsArgWith(2, null);
        queryStub.onCall(2).callsArgWith(2, null);

        return testedModule.editHeroImage(connectionStub, fakeQueriesObj, _postId, _post, function () {
            console.log(arguments); // --> {'0': null} as expected
            callbackSpy.apply(null, arguments);
        });
    });

    it('edits hero image', function () {
        expect(callbackSpy).has.been.calledWith(null);
    });
});
请注意,我已经将您的断言包装在一个descripe块中,这样我们就可以在
之前使用
。设置存根和执行类的实际逻辑已移动到
before
块,并添加了一个返回,这确保了异步函数在转移到断言之前完成


您的其他测试可能已经通过,但它们也会受到影响,这纯粹是一个时间问题

事实上@Varedis认为这是一个时间问题是正确的。然而,使用您的建议,将断言包装在descripe bloack中,并使用before函数设置测试,导致我的存根不再正常工作。然而,考虑到您关于计时的建议,我通过在测试套件中使用done回调成功地解决了这个问题。通过保持设置,我做了一个小小的改变,我的测试突然通过了:

it('edits hero image', function(done) {
    var _post = {
      id: '123',
      title: 'vf',
      sub_title: 'vf',
      slug: 'vf',
      reading_time: 4,
      created_at: '123',
      published_at: '123',
      deleted_on: false,
      hero_image: 'hero_image_path'
    };
    var _postId = '123';
    queryStub.onCall(0).callsArgWith(2, null, [{hero_image: '55'}]);
    queryStub.onCall(1).callsArgWith(2, null);
    queryStub.onCall(2).callsArgWith(2, null);
    testedModule.editHeroImage(connectionStub, fakeQueriesObj, _postId, _post, function() {
      callbackSpy.apply(null, arguments);
      expect(callbackSpy).has.been.calledWith(null);
      expect(callbackSpy).has.not.been.calledWith('FDgdjghg');
      done();
    });
  });

事实上,你认为这是一个时间问题是对的。然而,使用您的建议,将断言包装在descripe块中,并使用before函数设置测试,导致我的存根不再正常工作。然而,考虑到您关于计时的建议,我在测试套装中使用
done
回调成功地解决了这个问题。