Spy on Backbone.js View Functions with Jasmine - backbone.js

I'm trying to write a Jasmine spec to verify that a view's function is called when a model is added to the view's collection.
In the view's initialize function I do
this.collection.on('add', this.fooAdded, this);
In my Jasmine spec I'm doing:
describe('Foo View', function() {
it('should call fooAdded when a Foo is added', function() {
var view = new FooView({collection: new FooCollection()});
spyOn(view, 'fooAdded').andCallThrough();
view.delegateEvents();
view.collection.add({name: 'foo'});
expect(view.fooAdded).toHaveBeenCalled();
});
});
My implementation of fooAdded() logs something to the console, so I know it's being called. However the spy doesn't see that the fooAdded() has been called.
See my jsFiddle

Your problem is that spyOn replaces the spied on function with a new wrapper function but you're replacing it after the function has been used. By the time you call spyOn(view, 'fooAdded') to attach your spy, a reference to the original fooAdded is already in the collection's listener list so it is too late to spy on that callback.
If you spy on the fooAdded in the view's prototype before instantiating your view:
spyOn(FooView.prototype, 'fooAdded');
var view = new FooView(...);
view.delegateEvents();
//...
then things should work better. Demo: http://jsfiddle.net/m5Baw/1/ (thanks to amiuhle for updating the Jasmine links in the demo).
As an aside, I think something strange is going on your view, you shouldn't need to view.delegateEvents() outside the view's code so you might want to have a closer look at that.

I was about to respond and "mu is too short" is exactly on point. First thing to prove that your spy is not getting registered is to use following code instead of yours spyOn(view, 'fooAdded').andCallThrough();
spyOn(FooView.prototype, 'fooAdded').andCallFake(function(){
console.log('fake "fooAdded" called');
});
This will never be called as fooAdded is already registered with collection listener. Here is the updated jsFiddle

Related

Make jasmine test wait until dependent method call's promise is resolved

I have to test a void method which has a dependent method call that returns a promise and I can't mock that call as it is made on a local object created inside the tested method.
Is there a way to make jasmine expect calls to wait until the promise is resolved? I tried using $rootScope.$digest() but it is not ensuring that the dependent call's promise is resolved.
EDIT: Adding sample code
module.service('serviceToBeTested', ['$rootScope', 'someOtherService',
function($rootScope, someOtherService) {
var thirdPartyLib;
function fnToBeTested() {
//some validations and filtering on rootScope variable to build input for processing
thirdPartyLib = new ThirdPartyLib(); //this is not an angular service
var anotherFunction = function() {
//some hook functions that will be triggered by the third party library
}
// anotherFunction is set into thirdPartyLib so that hook functions will be triggered
thirdPartyLib.start().then(funtion() {
thirdPartyLib.someThing.load(); //this load will trigger one hook function
}
}
}]);
What I need to verify is that, upon invoking fnToBeTested(), a particular logic inside a hook function is executed (for that the control has to go inside the then part of thirdPartyLib.start()).
Actually this gets executed but only after the expect statements in the spec are executed.
And my spec file is almost like this:
it('should do this and this', function() {
// some initialization
serviceToBeTested.fnToBeTested();
$rootScope.$digest();
//expect statements
});
EDIT 2: Adding details on trial made as Andrew suggested below and adding clarity on how instance is created
ThirtPartyLib is instantiated inside main source as:
var theLib = require('theLib');
...............................
thirdPartyLib = new theLib.ThirdPartyLib();
And in spec, I created a var just like this and spied prototype as below:
var theLib = require('theLib');
................................
spyOn(theLib.ThirtPartyLib.prototype, 'start').and.callFake(.....);
But the fake function is not reached. When I check theLib.ThirtPartyLib.prototype in spec during debug, it lists the SpyStrategy while checking theLib.ThirtPartyLib.prototype in main source, it doesn't list that.
You should be able to test this with some clever use of mocking. In your beforeEach block, you can do something like this:
let promise; // declare this outside of your beforeEach so you have access to it in the specs
promise = $q.resolve(); // assign promise to
spyOn(ThirdPartyLib.prototype, 'start').and.returnValue(promise);
And then in your test, you now have access to the promise returned by start.

Jasmine: How to test a controller function calling other javascript function

Here is the sample code:
App.controller('Crtl', ["$scope", function ($scope) {
$scope.FetchDetail = function () {
var accNum = this.customer.accNo;
GetAccountDetails(accNum);
}; }]);
I am new to Jasmine and right now I am writing unit tests for my angular code. Here I created FetchDetail function which then calls javascript function GetAccountDetails(accNum).
How can I test this sample using Jasmine.
It depends if you need to stub it (i.e. capture and change its behaviour) or if it is enough to spy on it (watch it). Either way, you would be better off injecting it, so you can control it.
I have been using sinon http://sinonjs.org quite happily.
If you want to spy on it, i.e. watch its behaviour without changing it, then before calling it, you would do
var spy = sinon.spy(GetAccountDetails);
Then you can check if it was called, or what arguments were, etc.
spy.calledOnce === true; // if it was called once
spy.called === true; // if it was called at all
spy.firstCall.args[0]; // first argument to first call
// etc. - check out the docs
If you need to stub it, use the stub functionality, where you can then define what the behaviour should be. You get all of the spying features, but also control what the response is. However, it is hard to spy on a global, as opposed to a method of an existing object.

Listen to attribute changes in a backbone view

I'd like to call a method in my view when its attributes property is updated. The following code gives me an error: undefined is not a function.
SimpleView = Backbone.View.extend({
initialize: function(){
this.attributes = _.extend(this.attributes, Backbone.Events); // update
this.attributes.on('change', this.updateAttributes(), this);
}
});
How can I elegantly bind an event listener to the attributes?
JSFiddle here
UPDATE: I figured I have to extend attributes with Backbone.Events so I can listen to changes. Yeah... So now I don't get anymore errors, but still nothing happens. Any help would be largely appreciated.
Although I would recommend you to use a model for holding the attributes, if you want to stay with your current approach, you need to manually trigger the event.
this.attributes.trigger('change');
I have updated your JSFiddle so you can get an idea of how it works.

How to stub out a controller method called during object construction

I have an AngularJs controller that calls its own refresh() method while it is being constructed. The method in question accesses some template elements that are not present during unit testing.
function ListController($scope) {
/// ...
$scope.refresh = function() {
var tabId = angular.element('#id li.active a').attr('href');
//etc
}
//Initialise
$scope.refresh();
}
The refresh method causes unit tests to fail while the controller is being constructed. As the work it does is irrelevant to the tests, I want to override the method with a stub and simply test that it has been called.
Jasmine's Spy functionality looks like the way to go, but I can't find a way of setting one up for an object before it is constructed. How would I do this?
You should move this to a directive's link function. A link function is basically the result of a compile so Then you will know for sure that your element is compiled and ready, and that will make your "refresh" function unnecessary. In general, you should never access DOM via jqLite or jQuery from the controller. Also, the link function provides direct access to element, scope, and attributes (even href) which is nice.

Access $scope.property outside of a callback function in a controller

Can't think of a better title, sorry.
Please consider the following code -
//controller
function ProductCtrl($scope) {
getCategories(function (result) {
$scope.category = result.categories[0].name;
}); // asynchronouse method; getCategories calls uses XHR and returns the result as callback.
}
//view
{{category}}
When the view loads in the browser, getCategories gets called immediately. How do I make it load on demand, like onLoad on div or something so I can re-use this method somewhere else? Something like, $scope.getCategories returns the data I wanted, not on controller load. And how do I use this method in the view? e.g. <div onload=getCategories()></div> works?
Another question, the view does not print the category. How do I make $scope.category available outside of getCategories?
When the view loads in the browser, getCategories gets called immediately. How do I make it load on demand?
Wrap it in a function, and call that function when appropriate:
$scope.getCategories = function() {
getCategories(function (result) { ... }
}
so I can re-use this method somewhere else?
If multiple views need access to the result of getCatetories, you should create a service that stores the result. Your controllers can then inject that service to get access to it.
the view does not print the category.
Your AJAX is happening "outside" of Angular, so Angular doesn't know that a callback is being called and that $scope.category is being updated. Simply add $scope.$apply() after you update $scope.category in your callback and Angular will run a digest cycle and update your view.

Resources