chai-as-promise错误地通过了测试

时间:2014-08-18 18:23:16

标签: angularjs jasmine angular-promise chai

我正在尝试为返回角度承诺($ q库)的方法编写测试。

我很茫然。我正在使用Karma运行测试,我需要弄清楚如何确认AccountSearchResult.validate()函数返回一个promise,确认promise是否被拒绝,并检查使用promise返回的对象。

例如,正在测试的方法具有以下(简化):

.factory('AccountSearchResult', ['$q',
  function($q) {

  return {
    validate: function(result) {
      if (!result.accountFound) {
        return $q.reject({
          message: "That account or userID was not found"
        });
      }
      else {
        return $q.when(result);
      }
    }
  };
}]);

我以为我可以写一个这样的测试:

    it("it should return an object with a message property", function () {
      promise = AccountSearchResult.validate({accountFound:false});
      expect(promise).to.eventually.have.property("message"); // PASSES
    });

过去了,但这也是错误的:

    it("it should return an object with a message property", function () {
      promise = AccountSearchResult.validate({accountFound:false});
      expect(promise).to.eventually.have.property("I_DONT_EXIST"); // PASSES, should fail
    });

我正在尝试使用chai-as-promised'最终',但我的所有测试都通过了误报:

    it("it should return an object", function () {
      promise = AccountSearchResult.validate();
      expect(promise).to.eventually.be.an('astronaut');
    });

将通过。在查看文档和SO问题时,我看到了以下示例:

expect(promise).to.eventually.to.equal('something');
return promise.should.eventually.equal('something');
expect(promise).to.eventually.to.equal('something', "some message about expectation.");
expect(promise).to.eventually.to.equal('something').notify(done);
return assert.becomes(promise, "something", "message about assertion");
wrapping expectation in runs() block
wrapping expectation in setTimeout()

使用.should给我Cannot read property 'eventually' of undefined。我错过了什么?

1 个答案:

答案 0 :(得分:9)

事实证明,@ runTarm的建议都是正确的。我认为问题的根源是angular's $q library与角度的$摘要周期捆绑在一起。因此,在调用$ apply工作时,我相信原因它的工作原理是因为$ apply最终会调用$ digest。通常我认为$ apply()是让角度知道在其世界之外发生的事情的一种方式,并且我没有想到在测试的情况下,解决$ q的承诺.then() /可能需要在运行期望之前推动.catch(),因为$ q会直接变为角度。唉。

我能够以3种不同的方式使用它,一个用runs()块(和$ digest / $ apply),两个没有runs()块(和$ digest / $ apply)。

提供整个测试可能有点过分,但在寻找答案时我发现自己希望人们发布他们注入/存根/设置服务的方式以及不同的expect语法,所以我会发布我的整个测试。

describe("AppAccountSearchService", function () {
  var expect = chai.expect;

  var $q,
    authorization,
    AccountSearchResult,
    result,
    promise,
    authObj,
    reasonObj,
    $rootScope,
    message;

  beforeEach(module(
    'authorization.services',     // a dependency service I need to stub out
    'app.account.search.services' // the service module I'm testing
  ));

  beforeEach(inject(function (_$q_, _$rootScope_) {
    $q = _$q_;                  // native angular service
    $rootScope = _$rootScope_;  // native angular service
  }));

  beforeEach(inject(function ($injector) {
    // found in authorization.services
    authObj = $injector.get('authObj'); 
    authorization = $injector.get('authorization');

    // found in app.account.search.services
    AccountSearchResult = $injector.get('AccountSearchResult'); 
  }));

  // authObj set up
  beforeEach(inject(function($injector) {
    authObj.empAccess = false; // mocking out a specific value on this object
  }));

  // set up spies/stubs
  beforeEach(function () {
    sinon.stub(authorization, "isEmployeeAccount").returns(true);
  });

  describe("AccountSearchResult", function () {

    describe("validate", function () {

      describe("when the service says the account was not found", function() {

        beforeEach(function () {
          result = {
            accountFound: false,
            accountId: null
          };

          AccountSearchResult.validate(result)
            .then(function() {
              message = "PROMISE RESOLVED"; 
            })
            .catch(function(arg) {
              message = "PROMISE REJECTED";
              reasonObj = arg;
            });
          // USING APPLY... this was the 'magic' I needed
          $rootScope.$apply();
        });

        it("should return an object", function () {
          expect(reasonObj).to.be.an.object;
        });

        it("should have entered the 'catch' function", function () {  
          expect(message).to.equal("PROMISE REJECTED");
        });

        it("should return an object with a message property", function () {
          expect(reasonObj).to.have.property("message");
        });
        // other tests...
      });


      describe("when the account ID was falsey", function() {
        // example of using runs() blocks.
        //Note that the first runs() content could be done in a beforeEach(), like above
        it("should not have entered the 'then' function", function () {  
          // executes everything in this block first.
          // $rootScope.apply() pushes promise resolution to the .then/.catch functions
          runs(function() {
            result = {
              accountFound: true,
              accountId: null
            };

            AccountSearchResult.validate(result)
              .then(function() {
                message = "PROMISE RESOLVED"; 
              })
              .catch(function(arg) {
                reasonObj = arg;
                message = "PROMISE REJECTED"; 
              });

            $rootScope.$apply();
          });

          // now that reasonObj has been populated in prior runs() bock, we can test it in this runs() block.
          runs(function() {
            expect(reasonObj).to.not.equal("PROMISE RESOLVED");
          });

        });
        // more tests.....
      });

      describe("when the account is an employee account", function() {
        describe("and the user does not have EmployeeAccess", function() {

          beforeEach(function () {
            result = {
              accountFound: true,
              accountId: "160515151"
            };

            AccountSearchResult.validate(result)
              .then(function() {
                message = "PROMISE RESOLVED"; 
              })
              .catch(function(arg) {
                message = "PROMISE REJECTED";
                reasonObj = arg;
              });
            // digest also works
            $rootScope.$digest();
          });

          it("should return an object", function () {
            expect(reasonObj).to.be.an.object;
          });

          // more tests ...
        });
      });
    });
  });
});

现在我知道修复了,通过阅读测试部分下的$q docs显而易见,它明确地说要调用$ rootScope.apply()。由于我能够使用$ apply()和$ digest(),我怀疑$ digest真的需要调用,但是为了与文档保持一致,$ apply()可能是'最佳实践'

$apply vs $digest的体面细分。

最后,对我来说唯一的谜是为什么测试默认通过。我知道我达到了期望(他们正在运行)。那么为什么expect(promise).to.eventually.be.an('astronaut');会成功呢? /耸肩

希望有所帮助。感谢你推动正确的方向。