Sinon spy method that is called in backbone view initialize - backbone.js

I execute a method in the Backbone's View initialize method.
initialize : function(options) {
this.myMethod();
}
I am trying to spy on this method using sinon like:
this.spyMyMethod = sinon.spy(this.view, "myMethod");
end then
it('should call my method', function(){
expect(this.spyMyMethod).toHaveBeenCalledOnce();
});
but the test fails...
Any ideas?

You are spying on the method too late.
Wherever you are assigning this.view I assume it is from a call like new Views.SomeView(). It is that new call that will make the initialize function be executed.
Update
I don't really recommend doing this because it is pretty messy, but you can possibly do something like the following: (I don't know sinon but this is how you would do it with the base jasmine spy objects)
it('should call my method', function(){
var dummyView = new Views.SomeView();
spyOn(dummyView, "myMethod");
spyOn(Views, "SomeView").andCallFake(function () {
dummyView.initialize();
return dummyView;
});
new Views.SomeView();
expect(dummyView.myMethod).toHaveBeenCalled();
});
Another Possiblilty
Looks like it might be possible to override that method with a spy like below. If that works, it is probably the cleanest way to do this.
it('should call my method', function(){
spyOn(Views.SomeView.prototype, "myMethod");
new Views.SomeView();
expect(Views.SomeView.prototype.myMethod).toHaveBeenCalled();
});

you need to return a new instance of your view for the initialize method to be called.
I'm not sure if this.view = new View(); already however

Related

Unit test angular 1 directive with $on

Within this directive, I want to test the following:
$scope.$on('loggedMsg', function(){
if($scope.users.length){
$scope.callingFn();
}
});
I am able to emit loggedMsg and with $scope.apply(), it will call the $scope.callingFn(). Is there a way to not actually call $scope.callingFn, but just spy on it? I am using mocha and sinon to write these unit tests. Is what I am suggesting possible?
describe('testing directive', function(){
const elemScope = element.isolateScope;
it('should trigger callingFn if loggedMsg is emitted', function(){
scope.$emit('loggedMsg');
scope.$apply();
//elemScope.callingFn will be called due to the apply. Is there a way to just spy on that fn being called?
}
});
When you use Sinon to stub a function, calling that (now stubbed) function will not call the original.
Stubs also support the full Spy API, meaning we can use a Stub to make sure the original method doesn't get called and also see under what conditions it was called (such as how many times, with what parameters).
Using your example test:
describe('testing directive', function(){
const scope = element.isolateScope;
it('should trigger callingFn if loggedMsg is emitted', function(){
const stub = sinon.stub(scope, 'callingFn');
scope.$emit('loggedMsg');
scope.$apply();
assert(stub.called);
});
});
Documentation reference: http://sinonjs.org/releases/v4.1.2/stubs/

How am I able to test that a Backbone model's method bound to the event bus has fired when using Karma and Sinon?

When testing that a backbone model's event has fired with a sinon spy, it erroneously errors: expected doSomething to be called once but was called 0 times, even though it seems to execute when a console log is put in the method's body. The testing function looks like:
it('Y U NO WORK', function() {
const events = {};
_.extend(events, Backbone.Events);
const Model = Backbone.Model.extend({
initialize: function() {
this.listenTo(events, 'doSomething', this.doSomething);
},
doSomething: function() {},
});
const model = new Model();
const spy = sinon.spy(model, 'doSomething');
events.trigger('doSomething');
sinon.assert.calledOnce(spy);
});
I know that to fix, you'd have to put the sinon spy on the Model's prototype like const spy = sinon.spy(Model.prototype, 'doSomething'); in the line before the new Model() call, however it seems to work without issue when put in the model instance, like below:
it('And this does work', function() {
const Model = Backbone.Model.extend();
const model = new Model();
const spy = sinon.spy(model, 'set');
model.set('foo', 'bar');
sinon.assert.calledOnce(spy);
});
Curious why it needs to be put on the model's prototype in the first instance, but works on the model instance in the second?
Spy kind of replaces the original method with a custom one to know when it's invoked (It's holds reference to original for restoring later). So in the first case you set up an event listener before creating the spy. The event system is actually holding direct reference to the original method, not to the spy. Spy can do nothing about it, Spy wouldn't know when it is invoked.
You need to set up the spy first before setting up the event listener, something like:
it('Y U NO WORK', function() {
var spy;
const events = {};
_.extend(events, Backbone.Events);
const Model = Backbone.Model.extend({
initialize: function() {
spy = sinon.spy(this, 'doSomething');
this.listenTo(events, 'doSomething', this.doSomething);
//now same as this.listenTo(events, 'doSomething', spy);
},
doSomething: function() {},
});
const model = new Model();
events.trigger('doSomething');
sinon.assert.calledOnce(spy);
});
Or avoid keeping direct references to the original method like:
this.listenTo(events, 'doSomething', function() {
//by the time this is invoked, original has been replaced with spy
this.doSomething();
});
It'd work because it's not holding reference to original method, the method invokation is dynamic

How to test an AngularJS controller value that is set within a promise using Sinon

I'm having troubling testing a controller's value that's set within a promise returned by a service. I'm using Sinon to stub the service (Karma to run the tests, Mocha as the framework, Chai for assertions).
I'm less interested in a quick fix than I am in understanding the problem. I've read around quite a bit, and I have some of my notes below the code and the test.
Here's the code.
.controller('NavCtrl', function (NavService) {
var vm = this;
NavService.getNav()
.then(function(response){
vm.nav = response.data;
});
})
.service('NavService', ['$http', function ($http) {
this.getNav = function () {
return $http.get('_routes');
};
}]);
Here's the test:
describe('NavCtrl', function () {
var scope;
var controller;
var NavService;
var $q;
beforeEach(module('nav'));
beforeEach(inject(function($rootScope, $controller, _$q_, _NavService_){
NavService = _NavService_;
scope = $rootScope.$new();
controller = $controller;
}));
it('should have some data', function () {
var stub = sinon.stub(NavService, 'getNav').returns($q.when({
response: {
data: 'data'
}
}));
var vm = controller("NavCtrl", {
$scope: scope,
NavService: NavService
});
scope.$apply();
stub.callCount.should.equal(1);
vm.should.be.defined;
vm.nav.should.be.defined;
});
});
The stub is being called, i.e. that test passes, and vm is defined, but vm.nav never gets data and the test fails. How I'm handling the stubbed promise is, I think, the culprit. Some notes:
Based on reading elsewhere, I'm calling scope.$apply to set the value, but since scope isn't injected into the original controller, I'm not positive that will do the trick. This article points to the angular docs on $q.
Another article recommends using $timeout as what would "actually complete the promise". The article also recommends using "sinon-as-promised," something I'm not doing above. I tried, but didn't see a difference.
This Stack Overflow answer use scope.$root.$digest() because "If your scope object's value comes from the promise result, you will need to call scope.$root.$digest()". But again, same test failure. And again, this might be because I'm not using scope.
As for stubbing the promise, I also tried the sinon sandbox way, but results were the same.
I've tried rewriting the test using $scope, to make sure it's not a problem with the vm style, but the test still fails.
In the end, I could be wrong: the stub and the promise might not be the problem and it's something different and/or obvious that I've missed.
Any help is much appreciated and if I can clarify any of the above, let me know.
Sorry but a quick fix was all that you needed:
var stub = sinon.stub(NavService, 'getNav').returns($q.when({
response: {
data: 'data'
}
}));
Your promise is resolved to object containing response.data not just data
Checkout this plunk created from your code: https://plnkr.co/edit/GL1Xuf?p=preview
The extended answer
I have often fallen to the same trap. So I started to define the result returned from a method separately. Then if the method is async I wrap this result in a promise like $q.when(stubbedResult) this allow me to, easily run expectations on the actual result, because I keep the stubbed result in a variable e.g.
it('Controller should have some data', function () {
var result = {data: 'data'};
var stub = sinon.stub(NavService, 'getNav').returns($q.when(result));
var vm = controller(/* initController */);
scope.$apply();
stub.callCount.should.equal(1);
vm.nav.should.equal(result.data)
})
Also some tests debugging skill will come in handy. The easiest thing is to dump some data on the console just to check what's returned somewhere. Working with an actual debugger is preferable of course.
How to quickly catch mistakes like these:
Put a breakpoint at the $rootScope.apply() line (just before it is executed)
Put a breakpoint in the controller's NavService.getNav().then handler to see whether it is called and what it was called with
Continue with the debugger to execute the $rootScope.$apply() line. Now the debugger should hit the breakpoint set at the previous step - that's it.
I think you should use chai-as-promised
and then assert from promises like
doSomethingAsync().should.eventually.equal("foo");
or else use async await
it('should have some data', async function () {
await scope.$apply();
});
you might need to move then getNav() call in init kinda function and then test against that init function

Unit testing angular.extend function with jasmine spyOn

I have a directive, where, in certain case I use
angular.extend(dist, src)
Now I would like to test this case and check, if angular.extend is called.
I'm trying to use spyOn
spyOn(angular, 'extend')
And then in test
expect(angular.extend).toHaveBeenCalled()
Not sure I can do it at all, but I decided to give it a try.
Thanks for any help.
EDIT:
Here is my test, edited in accordance with your advise.
it('should create new scope and extend config if config is passed to directive', function() {
var spy = jasmine.createSpy('extendSpy').and.callThrough();
angular.extend = spy;
timeout.flush();
_.forEach(scope.accordionConfig, function(configItem) {
if (configItem.config) {
expect(angular.extend).toHaveBeenCalled();
}
});
});
In beforeEach hook I don't have anything special, just assigning config, creating some other preparation for rest tests and compiling the directive.
Here is a snippet from link function which I'm trying to test
if (scope.format === 'directive') {
if (scope.config) {
newScope = $rootScope.$new();
angular.extend(newScope, scope.config);
}
scope.content = $compile(scope.content)(newScope || scope);
}
console log the value of angular.extend before the assertion, it should be an instance of jasmine.spy, if it is not, there is a problem in the way that you create the spy, and we will need more context, perhaps your full code could help.
I assume you create the hook somewhere in the beforeEach hook or on one of the other hooks?
Try the following code:
var spy = jasmine.createSpy('extendSpy').and.callThrough();
angular.extend = spy;
expect(spy).toBeCalledWith(dist, src);

AngularJS - How to test if a function is called from within another function?

I'm trying to get started with karma-jasmine and I'm wondering why this test fails:
it("should call fakeFunction", function() {
spyOn(controller, 'addNew');
spyOn(controller, 'fakeFunction');
controller.addNew();
expect(controller.fakeFunction).toHaveBeenCalled();
});
In my controller that I've previously set up for this test I have the following:
function addNew() {
fakeFunction(3);
}
function fakeFunction(number) {
return number;
}
both addNew and fakeFunction are exposed using:
vm.addNew = addNew;
vm.fakeFunction = fakeFunction;
The test, however, fails with the following:
Expected spy fakeFunction to have been called.
I can make the test pass if I call the function from within my test. I was hoping, however, I could test if fakeFunction was called by another function. What is the proper way to achieve this?
Update:
//test.js
beforeEach(function() {
module("app");
inject(function(_$rootScope_, $controller) {
$scope = _$rootScope_.$new();
controller = $controller("CreateInvoiceController", {$scope: $scope});
});
});
If I test something like:
it('should say hello', function() {
expect(controller.message).toBe('Hello');
});
The test passes if I put the following in my controller:
var vm = this;
vm.message = 'Hello';
I just want to know how I can test if a public function was called from another function.
Your addNew method is calling fakeFunction. However, it is not calling controller.fakeFunction, which is what your expectation is.
You'll need to change your code to use your controller, rather than these independent functions.
EDIT: You also need to not spy on your addNew function. This is causing the function to be replaced with a spy. The other alternative is to change it to:
spyOn(controller, 'addNew').and.callThrough()
I just came across this problem myself. The previous answer by #Vadim has the right principles but I don't think everything was very clear. In my case, I am trying to call a public function on a service from within another function. Here are the relevant snippets:
Service:
angular.module('myApp').factory('myService', function() {
function doSomething() {
service.publicMethod();
}
function publicMethod(){
// Do stuff
}
var service = {
publicMethod: publicMethod
};
return service;
});
Test:
it('calls the public method when doing something', function(){
spyOn(service, 'publicMethod');
// Run stuff to trigger doSomething()
expect(service.publicMethod).toHaveBeenCalled();
});
The key here is that the function being tested needs to be calling the same reference as the public function that is being spy'd on.

Resources