我正在尝试为返回角度承诺($ q库)的方法编写测试。
我很茫然。我正在使用Karma运行测试,我需要弄清楚如何确认该AccountSearchResult.validate()
函数返回了一个承诺,确认该承诺是否被拒绝,以及检查随该承诺返回的对象。
例如,被测试的方法具有以下(简化的):
.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-promise,但我的所有测试均以误报通过:
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
。我想念什么?
事实证明,@runTarm的建议都是正确的。我认为问题的根源是angular的$ q库与angular的$ digest周期捆绑在一起。因此,在调用$ apply时,我相信它起作用的原因是因为$ apply最终仍然会调用$ digest。通常,我认为$ apply()是一种让角度了解其世界之外发生的事情的方法,但在我看来,在测试的情况下,解决$ q promise的问题.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文档,可以很明显地看出来,它专门说要调用$ rootScope.apply()。由于我能够同时使用$ apply()和$ digest()来工作,因此我怀疑$ digest确实是需要调用的东西,但与文档保持一致,$ apply()可能是“最佳实践” 。
$ apply与$ digest的故障情况不错。
最后,剩下的唯一谜团就是默认情况下测试通过的原因。我知道我正在达到期望(它们正在运行)。那么为什么会expect(promise).to.eventually.be.an('astronaut');
成功呢?/耸肩
希望能有所帮助。感谢您朝着正确的方向前进。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句