I'm working with backbone and jasmine and now
trying to test the callCount of 'sync' method, when model saved.
For some strange reason the sync handler continue to handle the sync even after the done variable is true (this is there i planned to stop the test)
I'm a newbie for jasmine so i guess i didn't understand something elementar here...
here is my speck:
describe('Model :: User', function() {
var mockData = { name: 'Foo Bar' };
beforeEach(function() {
var that = this,
done = false;
require(['app/namespace','app/models/UserModel','app/collections/UsersCollection'], function(namespace, UserModel ,UsersCollection) {
that.users = new UsersCollection();
that.user = new UserModel();
done = true;
});
waitsFor(function() {
return done;
}, "Create Models");
});
afterEach(function(){
var done = false,
isDone = function(){ return done; };
this.users.fetch({
success: function(c) {
console.log('after the test calling destory of collection...')
c.each(function(m){
m.destroy();
});
done = true;
}
});
waitsFor(isDone);
done = false;
this.user.destroy({
success: function(){
console.log('after the test calling destory of model...')
done = true;
}
});
waitsFor(isDone);
});
describe('.save()', function() {
it('should call sync when saving', function() {
var done = false,
spy = jasmine.createSpy();
this.user.on('sync', spy);
this.user.on('sync', function(){
console.log('checking spy.callCount-'+spy.callCount);
//------------------------------------------------
if(!done)//why i need this if ?!
expect(spy.callCount).toEqual(1);
done = true;
}, this);
this.user.save(mockData);
waitsFor(function() { return done; });
});
});
});
The test workiing correctly only if i add "if(!done)" condition before expect statement,
otherwise it continue to count sync calls that caused by destroy after the test...
Thanks forwards
There are some of issues with this test. First of all, you dont need to test that the sync event is fired when saving your model cause this is provided by another framework, which is hopefully tested.
Second you should use the fake server of SinonJs to not mess with async calls. With sinon your request will be called immediately, which means you dont need waitsFor. Also assertions in callback seems a bit odd.
this.server = sinon.fakeServer.create();
server.respondWith({data: 'someData'})
server.autoRespond = true; //so when the request start the fake server will immediately call the success callback
var spy = jasmine.createSpy();
this.user.on('sync', spy);
this.user.save(mockData);
expect(spy.callCount).toEqual(1);
Related
Trying to write a jasmine test for the below code...
refreshCacheIfNewVersionIsAvailable();
//Check if a new cache is available on page load and reload the page to refresh app cache to the newer version of files
function refreshCacheIfNewVersionIsAvailable() {
$window.addEventListener('load', function (e) {
$window.applicationCache.addEventListener('updateready', function (e) {
if ($window.applicationCache.status == window.applicationCache.UPDATEREADY) {
// Manifest changed. Now Browser downloadeds a new app cache.
alert(textService.versioning.newVersionMessage);
$window.location.reload(true);
} else {
// Manifest didn't change. Nothing new to server.
}
}, false);
}, false);
}
Your challenge
I assume the challenge you are facing is that you are unable to see how to test the code in the callback functions. You just have to realize that you have access to the callback function when you spy on addEventListener, after the spy is executed in your service under test (refreshCacheIfNewVersionIsAvailable). Since you can get a reference to it, you can execute it, just as if it was the function you were testing.
Sample solution
The following is untested, written off the top of my head, but something along the lines of what I would expect to write if I had to test that code.
describe('refreshCacheIfNewVersionIsAvailable()', function() {
beforeEach(function() {
spyOn($window, 'addEventListener');
});
it('should register a load event handler on the window', function() {
refreshCacheIfNewVersionIsAvailable();
expect($window.addEventListener.calls.count()).toBe(1);
var args = $window.addEventListener.calls.argsFor(0);
expect(args.length).toBe(3);
expect(args[0]).toBe('load');
expect(typeof args[1]).toBe('function');
expect(args[2]).toBe(false);
});
describe('load event', function() {
var loadFunction;
beforeEach(function() {
refreshCacheIfNewVersionIsAvailable();
var args = $window.addEventListener.calls.argsFor(0);
loadFunction = args[1];
spyOn($window.applicationCache, 'addEventListener');
});
it('should register an updateready event handler in the window application cache', function() {
loadFunction();
expect($window.applicationCache.addEventListener.calls.count()).toBe(1);
var args = $window.applicationCache.addEventListener.calls.argsFor(0);
expect(args.length).toBe(3);
expect(args[0]).toBe('updateReady');
expect(typeof args[1]).toBe('function');
expect(args[2]).toBe(false);
});
describe('updateready event', function() {
var updateReadyFunction;
beforeEach(function() {
loadFunction();
var args = $window.applicationCache.addEventListener.calls.argsFor(0);
updateReadyFunction = args[1];
});
it('should reload the window if the status is UPDATEREADY', function() {
// You get the point
});
});
});
});
I have a page developed with angular 1.5, it shows a loading progress while initial API call, I want to write test case for this. I'm using a variable this.loaded to handle the loading progress. Initially this.loaded = false and once everything is loaded it will be this.loaded =true, but when I write test case this.loaded is alway true since the test case is checking final value of the variable. How I can slow down the flow and check the progress is getting triggered or not using Jasmine test?
HTML:
<loading-directive is-loaded='$ctrl.loaded'>
DOM to show after loaded
</loading-directive>
JS:
function componentCtrl (someFactory) {
var _this = this;
_this.loaded = false;
someFactory.someMethod().then(function () {
_this.loaded = false;
});
}
I want to check the _this.loaded = false before the call happens.
You cannot check the value of _this.loaded before the factory call. A work-around that you could do to achive that is to mock the factory call in a way that it doesn't change the _this.loaded and you can still check it is false. I will create an example that might give you an insight. I hope it helps:
describe('MyController Spec', function() {
// Load module
beforeEach(angular.mock.module('myApp'));
it('Check if loaded is false if factory call does not change its value', inject(function($controller) {
//setup
spyOn(someFactory, 'someMethod').and.callFake(function() {
//create a promise mock that will not execute the code that
//changes _this.loaded
var uselessPromiseMock = {
then: function() {}
};
return uselessPromiseMock;
});
//action
var myController = $controller("componentCtrl");
//assert
expect(myController.loaded).toBeFalsy();
}));
it('Check if loaded is true after a normal controller initialization', inject(function($controller) {
//action
var myController = $controller("componentCtrl");
//assert
expect(myController.loaded).toBeTruthy();
}));
});
PS: I'm assuming you mistyped and the callback that you pass to the factory call changes _this.loaded to true :)
I'm trying to write tests for a method that returns an angular promise ($q library).
I'm at a loss. I'm running tests using Karma, and I need to figure out how to confirm that the AccountSearchResult.validate() function returns a promise, confirm whether the promise was rejected or not, and inspect the object that is returned with the promise.
For example, the method being tested has the following (simplified):
.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);
}
}
};
}]);
I thought I could write a test like this:
it("it should return an object with a message property", function () {
promise = AccountSearchResult.validate({accountFound:false});
expect(promise).to.eventually.have.property("message"); // PASSES
});
That passes, but so does this (erroneously):
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
});
I am trying to use the chai-as-promised 'eventually', but all my tests pass with false positives:
it("it should return an object", function () {
promise = AccountSearchResult.validate();
expect(promise).to.eventually.be.an('astronaut');
});
will pass. In looking at docs and SO questions, I 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 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()
Using .should gives me Cannot read property 'eventually' of undefined. What am I missing?
#runTarm 's suggestions were both spot on, as it turns out. I believe that the root of the issue is that angular's $q library is tied up with angular's $digest cycle. So while calling $apply works, I believe that the reason it works is because $apply ends up calling $digest anyway. Typically I've thought of $apply() as a way to let angular know about something happening outside its world, and it didn't occur to me that in the context of testing, resolving a $q promise's .then()/.catch() might need to be pushed along before running the expectation, since $q is baked into angular directly. Alas.
I was able to get it working in 3 different ways, one with runs() blocks (and $digest/$apply), and 2 without runs() blocks (and $digest/$apply).
Providing an entire test is probably overkill, but in looking for the answer to this I found myself wishing people had posted how they injected / stubbed / setup services, and different expect syntaxes, so I'll post my entire test.
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 ...
});
});
});
});
});
Now that I know the fix, it is obvious from reading the $q docs under the testing section, where it specifically says to call $rootScope.apply(). Since I was able to get it working with both $apply() and $digest(), I suspect that $digest is really what needs to be called, but in keeping with the docs, $apply() is probably 'best practice'.
Decent breakdown on $apply vs $digest.
Finally, the only mystery remaining to me is why the tests were passing by default. I know I was getting to the expectations (they were being run). So why would expect(promise).to.eventually.be.an('astronaut'); succeed? /shrug
Hope that helps. Thanks for the push in the right direction.
I need to create a user in kinvey and assert that the user has been added, all within an angular test. I am using the karma test runner. All my of tests are timing out, and none of the code inside the kinvey promise blocks is being executed. How can I go about writing these tests? Test code is below:
describe("Kinvey: Users", function() {
var key,
secret;
beforeEach(function(){
key = '...',
secret = '...'
});
it("should create a user", function(done){
var App = angular.module('App', ['kinvey']);
App.run(['$kinvey', function($kinvey) {
$kinvey.init({
appKey: key,
masterSecret: secret
});
var promise = $kinvey.DataStore.save('users', {
username : 'gertrude#test.com'
});
promise().then(function(success){
var query = new $kinvey.Query();
query.equalTo('username', 'gertrude');
var queryPromise = $kinvey.DataStore.find('users', query);
return queryPromise().then(
function(response){
console.log("response");
expect(response.username).to.equal("gertrude#test.com");
var destroyPromise = $kinvey.DataStore.destroy('users', response.id);
return destroyPromise().then(function(success){
return done();
}, function(error){
return done();
})
}, function(error){
return null;
})
}, function(error){
return null;
});
}]);
});
You are missing a few things:
You are missing a call to angular.bootstrap(), so the run block is never executed.
The $kinvey.init() method is asynchronous. So, before calling $kinvey.DataStore.save() (or any other $kinvey.* method for that matter), make sure the init method finishes.
It seems you are creating users by using $kinvey.DataStore.save() to the users collection. It is highly recommended that you use $kinvey.User.signup() instead.
The best way to go forward is to move things to the before hook:
before(function(done) {
this.App = angular.module('App', ['kinvey']);
this.App.run(['$kinvey', function($kinvey) {
$kinvey.init({
appKey : 'App Key',
appSecret : 'App Secret'
}).then(function() {
done();
}, function(error) {
done(new Error(error.description));
});
}]);
angular.bootstrap(document, ['kinvey']);
});
Now, in your test (it method), you can obtain a reference to $kinvey by doing:
var $injector = angular.injector(['ng', 'kinvey']);
$injector.invoke(function($kinvey) {
// Do your tests with $kinvey.
});
Test away! I put a JSFiddle here.
Disclaimer: I am the author of the library.
I'm new to AngularJS. I'm currently looking at the $timeout service. I understand that it's like a wrapper for the setTimeout function. The documentation says that it provides exception handling. In addition, the documentation says I can cancel and flush a timeout.
Can someone please explain to me when an exception would happen with a timeout? I also don't understand why I need to flush a timeout. I would love an explanation or maybe a jsfiddle. For the life of me, I can't figure out why or even how to use these additional features.
Update:
When I attempt to run the stop function, the catch handler associated with myTimer get's thrown. Here is my code:
var myTimer = null;
$scope.hasStarted = false;
$scope.start = function () {
if ($scope.hasStarted === false) {
$scope.isTimerActive = true;
myTimer = $timeout(function () { $scope.isTimerActive = false; }, 5000);
myTimer.catch(function (err) {
alert("An error happened with the clock.");
});
}
}
$scope.stopClock = function () {
$timeout.cancel(myTimer);
$scope.isClockActive = false;
}
Thank you!
$timeout is most awesome indeed.
Exception handling
$timeout returns a promise which can have an error state. For example
var timePromise = $timeout(function(){
throw new Error('I messed up');
}, 10000);
timePromise.catch(function(err){
// do something with the error
});
read all about promises here.
Cancel
Canceling a $timeout is easy. Instead of using clearTimeout you pass the promise back.
var timePromise = $timeout(function(){
// do thing
}, 23432);
// wait I didn't mean it!
$timeout.cancel(timePromise);
Flush
Flush is most useful for unit testing, ultimately it fires any outstanding callbacks.
$timeout(function(){
console.log('$timeout flush');
}, 222);
$timeout(function(){
console.log('rocks my face');
}, 234232);
$timeout.flush(); // both console logs will fire right away!
or this file:
var itsDone = false;
$timeout(function(){
itsDone = true;
}, 5000);
with this test:
// old no flush way (async)
it('should be done', function(done){
expect(isDone).to.be.false;
setTimeout(function(){
expect(isDone).to.be.true;
done();
}, 5001);
});
// now with flush
it('should be done', function(){
expect(isDone).to.be.false;
$timeout.flush();
expect(isDone).to.be.true;
});