angularjs - chai-as-promised erroneously passes tests -
i'm trying write tests method returns angular promise ($q library).
i'm @ loss. i'm running tests using karma, , need figure out how confirm accountsearchresult.validate() function returns promise, confirm whether promise rejected or not, , inspect object returned promise.
for example, method being tested has following (simplified):
.factory('accountsearchresult', ['$q', function($q) { return { validate: function(result) { if (!result.accountfound) { return $q.reject({ message: "that account or userid not found" }); } else { return $q.when(result); } } }; }]); i thought write test this:
it("it should return object message property", function () { promise = accountsearchresult.validate({accountfound:false}); expect(promise).to.eventually.have.property("message"); // passes }); that passes, (erroneously):
it("it should return object message property", function () { promise = accountsearchresult.validate({accountfound:false}); expect(promise).to.eventually.have.property("i_dont_exist"); // passes, should fail }); i trying use chai-as-promised 'eventually', tests pass false positives:
it("it should return object", function () { promise = accountsearchresult.validate(); expect(promise).to.eventually.be.an('astronaut'); }); will pass. in looking @ docs , questions, have seen examples such as:
expect(promise).to.eventually.to.equal('something'); return promise.should.eventually.equal('something'); expect(promise).to.eventually.to.equal('something', "some message expectation."); expect(promise).to.eventually.to.equal('something').notify(done); return assert.becomes(promise, "something", "message assertion"); wrapping expectation in runs() block wrapping expectation in settimeout() using .should gives me cannot read property 'eventually' of undefined. missing?
@runtarm 's suggestions both spot on, turns out. believe root of issue angular's $q library tied angular's $digest cycle. while calling $apply works, believe reason works because $apply ends calling $digest anyway. typically i've thought of $apply() way let angular know happening outside world, , didn't occur me in context of testing, resolving $q promise's .then()/.catch() might need pushed along before running expectation, since $q baked angular directly. alas.
i able working in 3 different ways, 1 runs() blocks (and $digest/$apply), , 2 without runs() blocks (and $digest/$apply).
providing entire test overkill, in looking answer found myself wishing people had posted how injected / stubbed / setup services, , different expect syntaxes, i'll post entire test.
describe("appaccountsearchservice", function () { var expect = chai.expect; var $q, authorization, accountsearchresult, result, promise, authobj, reasonobj, $rootscope, message; beforeeach(module( 'authorization.services', // dependency service need stub out 'app.account.search.services' // 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 beforeeach(inject(function($injector) { authobj.empaccess = false; // mocking out specific value on object })); // set spies/stubs beforeeach(function () { sinon.stub(authorization, "isemployeeaccount").returns(true); }); describe("accountsearchresult", function () { describe("validate", function () { describe("when service says account 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... 'magic' needed $rootscope.$apply(); }); it("should return object", function () { expect(reasonobj).to.be.an.object; }); it("should have entered 'catch' function", function () { expect(message).to.equal("promise rejected"); }); it("should return object message property", function () { expect(reasonobj).to.have.property("message"); }); // other tests... }); describe("when account id falsey", function() { // example of using runs() blocks. //note first runs() content done in beforeeach(), above it("should not have entered 'then' function", function () { // executes in block first. // $rootscope.apply() pushes promise resolution .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(); }); // reasonobj has been populated in prior runs() bock, can test in runs() block. runs(function() { expect(reasonobj).to.not.equal("promise resolved"); }); }); // more tests..... }); describe("when account employee account", function() { describe("and user 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 works $rootscope.$digest(); }); it("should return object", function () { expect(reasonobj).to.be.an.object; }); // more tests ... }); }); }); }); }); now know fix, obvious reading $q docs under testing section, says call $rootscope.apply(). since able working both $apply() , $digest(), suspect $digest needs called, in keeping docs, $apply() 'best practice'.
decent breakdown on $apply vs $digest.
finally, mystery remaining me why tests passing default. know getting expectations (they being run). why expect(promise).to.eventually.be.an('astronaut'); succeed? /shrug
hope helps. push in right direction.
Comments
Post a Comment