I'm trying to spy on a Backbone view. Within one of the view function, an object is created and attached to the view:
var BackboneView = Backbone.View.extend({
anObject: null,
.....
viewFunction: function() {
if (!this.anObject) {
this.anObject = new theObject();
this.anObject.setTimeout(10);
}
}),
....
});
return BackboneView;
The object looks like this (and cannot be changed):
var theObject = function () {
this.setTimeout = function (timeout) {
//set timeout
};
return this;
});
return theObject;
I want to make sure setTimeout was called. So I tried:
it('theObject.prototype.setTimeout called', function () {
spyOn(theObject.prototype, 'setTimeout');
myBackboneView.viewFunction()
expect(theObject.prototype.setTimeout).toHaveBeenCalled();
});
But I get the error:
setTimeout() method does not exist
So I try to spy on it through the Backbone view:
it('myBackboneView.anObject.setTimeout called', function () {
spyOn(myBackboneView.anObject, 'setTimeout');
myBackboneView.viewFunction();
expect(myBackboneView.anObject.setTimeout).toHaveBeenCalled();
});
But I predictably get the error:
spyOn could not find an object to spy upon for setTimeout()
Any ideas how I make sure setTimeout() was called?
setTimeout() method does not exist
Because you did not set setTimeout on the function prototype.
if you want a function on a prototype you need to put it on a prototype,then you can spy on the function.
theObject.prototype.setTimeout=function(){/*some code*/}
and no need to return this in the constructor.
this.setTimeout = function (timeout) {
//set timeout
};
return this; // this is wrong!!!
Related
I would like a function I am spying on not execute until some condition is true.
Here is an example:
function openInfoDialog(id) {
let scope = $scope.$new(true);
scope.dataLoading = true;
api.getData(id).then(data => {
let processedData = process(data);
scope.columns = processedData.columns;
scope.data = processedData.data;
scope.dataLoading = false;
});
ngDialog.open({
//various dialog params,
scope
});
}
In my test I am trying to verify how the data is returned by the process function. The only way I can see to check that is to spy on ngDialog.open and check what is was called with.
However in my test ngDialog is being called before the process function finishes, meaning scope.data is undefined.
Is there any way I can tell ngDialog to wait until the then block is complete in the test? Or wait until scope.dataLoading is true.
To be clear, I do not want to change the functionality in my controller, I need to test what is currently written above.
Thanks for any help.
Spy on api.getData and mimic a promise return which is sync.
it('should process data', function () {
var mockedDataObject = { ... };
spyOn(api, 'getData').and.returnValue({
then: function () { // Mimic a promise
return mockedDataObject;
}
});
openInfoDialog(123);
expect(api.getData).toHaveBeenCalledWith(123);
expect(scope.columns).toEqual(whateveryouexpect)
expect(scope.data).toEqual(whateveryouexpect)
expect(scope.dataLoading).toEqual(whateveryouexpect)
});
P.S. you mentioned you want to
verify how the data is returned by the process function
In order to do that appropriately, firstly bind the process function onto scope, so that it can be accessed from tests. Secondly, call that function directly and check what it returned. You are meant to test functionality in isolation.
it('should process data', function () {
var mockedDataObject = { ... };
var mockedProcessedData = scope.process(mockedDataObject);
expect(mockedProcessedData.columns).toEqual(expectedcolumns);
expect(mockedProcessedData.data).toEqual(expecteddata);
});
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
(function () {
'use strict';
describe('TestButton', function () {
var controller, element, util;
beforeEach(function () {
module('app');
module('template');
module('TestUtil');
inject(function (TestUtilService) {
util = TestUtilService;
var testFunc = function foo(){
return 5;
};
// Passing the defined function to the custom directive and call it via
// controller.clickButton (it will be fired there)
// function=\' + testFunc + '" passing the function as string also not working
element = '<custom-button id="btn1" label="Click Me" function="testFunc"></custom-button>';
element = util.compileElement(element);
controller = util.getController(element, 'customButton');
});
});
it('should be resolved', function () {
expect(element.html()).toContain('input');
});
it('should be set', function () {
expect(element.find('input').val()).toBe('Click Me');
});
it('should be set', function () {
expect(element.find('input').attr('id')).toContain('button_btn1_');
});
it('should fire the passed function', function () {
//controller.clickButton() should log 5 or something, but its not working at all
//controller itself is working perfect (I have access to the whole directive-scope)
});
});
TestUtilService :
this.compileElement = function (element) {
var el = vm.$compile(element)(vm.$scope);
vm.$scope.$digest();
return el;
};
this.getController = function (element, directiveName) {
return element.controller(directiveName);
}
How can I pass an object to the directive, compile it and access it. I also have problems with passing arrays or something. How does that work in Karma/Jasmine??
Would be wonderful, if someone could give me some tips.
From what I've experienced, you can't do this directly through the directive alone; however, if you want to add the functionality of the function (or the object), you can shim it in through whichever isolate scope object you're using in the test.
However, you should not be asserting on its existence. A test that fires your custom function will pass if the function is excluded:
scope.testFunc = function(){console.log(1)};
element = '<custom-button id="btn1" label="Click Me"></custom-button>';
This is because the scope already has the function it cares about.
One thing to do in this scenario is to set up a condition in which the function is passed in and is presumed to be working; effectively, you'd be injecting the function in for testing purposes. May be worth mocking at this point to ensure that it fires in the way you expect it to, but assertions beyond that are not going to be valid.
You have to define your passing object at the created scope in Karma/Jasmine like so:
scope.testFunc = function(){console.log(1)};
element = '<custom-button id="btn1" label="Click Me" function="testFunc"></custom-button>';
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.
I have a mocked resource that is simply an object of functions, in this case only one. This function returns another function. The code looks like this:
var mockRevivhalResource = {
tenantOptions: function tenantOptions() {
return {
post: function post() {
}
}
}
};
var RevivhalResource = mockRevivhalResource;
I am able to spy on tenantOptions() with spyOn(RevivhalResource, 'tenantOptions'); but I can't seem to spy on post() that tenantOptions() returns.
The only thing that doesn't throw an error is spyOn(RevivhalResource.tenantOptions(), 'post'); but I believe I am simply setting the spy on a new instance on the post function, but when the angular application calls RevivhalResource.tenantOptions().post(); it's calling a new instance of post because if I have console.log("test"); in the mocked post function the tests print out "test" even if I don't let the spy callThrough. But I could be wrong on that matter.
The controller code that is calling the resource is like this
$scope.fun = function fun() {
RevivhalResource.tenantOptions().post(
{...stuff...},
function success(data) {...success handler...},
function error(data) {...error handler...}
)
};
with RevivhalResource being a angularjs provider like this
angular.module('revivhal').provider('RevivhalResource', [function () {
...init stuff...
this.$get = function ($resource) {
...more init stuff...
return {
tenantOptions: function tenantOptions() {
return $resource(...path..., {...data...},
post: {method: "POST"});
}
}
}
}]);
And what I'm trying to do is using a spy to confirm that RevivhalResource.tenantOptions().post() has been called. I then need to make the spy call the success and error handlers to test that the handlers are correct.
You can refactor your mock so its build out of spies that return other spies:
var postSpy = jasmine.createSpy();
var tenantOptionsSpy = jasmine.createSpy()
var mockRevivhalResource = {
tenantOptions: tenantOptionsSpy.andReturn({post: postSpy})
};
mockRevivhalResource.tenantOptions().post()
expect(tenantOptionsSpy).toHaveBeenCalled();
expect(postSpy).toHaveBeenCalled();