Create a spy for a function in a class - jasmine

I'm adding Jasmine to a large project in order to add tests to that project's javascript. Normally I use Ruby and I'm a little out of my element here.
I have a class, that has a function and I want to create a spy for it so that it returns a certain value during one of my tests. Here's a summary of the code:
class #MyKlass
current_location = ->
window.location.host
verify_domain: () ->
domain_filter = current_location()
domain_list = /example\.com/i
#valid_domain = domain_filter.match(domain_list)?
So how would I do something like this?
it("verifies domains", function() {
spyOn(MyKlass, 'current_location').and.returnValue("example");
var myKlass = new MyKlass();
expect(myKlass.verify_domain()).toEqual(true);
});

So it turns out that this was a function on an instance of the class- which I had just missed somehow in the code. Here's the abbreviated solution:
describe('MyKlass', function() {
myKlass = null
beforeEach(function() {
myKlass = new MyKlass()
});
it('returns true for our domains', function() {
spyOn(myKlass, 'verify_domain').and.returnValue(true);
expect(myKlass.verify_domain()).toBe(true);
});

Related

Is there a simple way for to check if method was not called when a variable is undefined?

So I have a simple method that executes something only if the passed variable is defined:
public myFunction(item) {
if (typeof item !== 'undefined') {
item.doSomething();
}
}
Here's my test in jasmine:
describe('myFunction()', () => {
it ('should only do something if the item passed is defined.', () => {
const item = new Item();
spyOn(item, 'doSomething');
service.myFunction(item);
//this works
expect(item.doSomething).toHaveBeenCalledTimes(1);
});
it ('should not do something if the item passed is undefined.', () => {
const item = undefined;
spyOn(item, 'doSomething');
service.myFunction(item);
//this does not work..
expect(item.doSomething).toHaveBeenCalledTimes(0);
});
});
My first test works fine. But I do not know how to express my second test. How can I say that doSomething was never called when the item passed is undefined? It seems quite trivial, but I'm having trouble with this. I have a feeling it's not possible, because I can't spy on something that is undefined. Then again, maybe there is a work around?
Try:
it ('should not do something if the item passed is undefined.', () => {
const item = undefined;
const conditionForIf = typeof item !== 'undefined';
// check the conditionForIf, if it is false, it won't go on and `doSomething`
expect(conditionForIf).toBe(false);
});

How spy works with Jasmine in Angular 7?

I have created this spy using spyOn
it("spyon ", () => {
const searchChangeEmitSpy = spyOn(Adders.countlist,"add");
expect(searchChangeEmitSpy.calls.count()).toEqual(2);
});
and inside Adder class I have the following function
countlist(){ const i =0;
this.quoteList.forEach(element => {
console.log(element);
this.add(4,i++);
});
}
length of quoteList array is 2
what I am getting as a result
Error: : add() method does not exist
I don't think you can directly spy on the function of the class Adders like this, instead spy on the prototype or create an instance of the class and spy on that. I would use two spies and implement it like this:
it("spyon", () => {
const countlistSpy = spyOn(Adders.prototype, 'countlist');
const addSpy = spyOn(Adders.prototype, 'add');
// call your function / trigger something that calls the function
expect(countlistSpy).toHaveBeenCalledTimes(1);
// more expectations here
});
Or with an instance of the class in the beforeEach block you can define your instance like this:
let adder: Adders = new Adders();
And then your test would look like this:
it("spyon", () => {
const countlistSpy = spyOn(adder, 'countlist');
const addSpy = spyOn(adder, 'add');
// call your function / trigger something that calls the function
expect(countlistSpy).toHaveBeenCalledTimes(1);
// more expectations here
});
With the help of Fabian answer, I able to debug and solve my problem. Actually, I need to trigger the function inside the class on which I was spying. after doing so, it gave me the expected output.
test case
it("spyOn countList add()", () => {
const searchChangeEmitSpy = spyOn(Adders,"add");
Adders.addNewQuote("This is my second post");
Adders.countlist(0);
expect(searchChangeEmitSpy.calls.count()).toEqual(2);
});
function inside the class to be spied
countlist(i:number){
this.quoteList.forEach(element => {
console.log(element);
this.add(4,i++);
});
//return i;
}

Extending Kendo's AgendaView (from Scheduler) using TypeScript

I am trying to extend the kendo scheduler using typescript.
Here is a working example using JS: http://jsbin.com/ucaB/1/edit
But I can't get it working in TypeScript.
I think I am close:
var CustomAgenda = (<any>kendo.ui).AgendaView.extend(
{
endDate: () =>
{
var self = this;
var d = (<any>kendo.ui).AgendaView.fn.endDate.call(self);
return (<any>kendo).date.addDays(d, 21);
}
});
But obviously my 'this' reference is wrong. The kendo typings file also doesn't expose kendo.date, or AgendaView, which makes things a little messy.
Maybe I am going about it wrong, this sure feels ugly...
In this particular case you dont want to capture the this outside the endDate function. So just use function and not ()=>:
var CustomAgenda = (<any>kendo.ui).AgendaView.extend(
{
endDate: function()
{
var self = this;
var d = (<any>kendo.ui).AgendaView.fn.endDate.call(self);
return (<any>kendo).date.addDays(d, 21);
}
});
This video might help clear things up a bit : https://www.youtube.com/watch?v=tvocUcbCupA&hd=1
Here is a more 'typescripty' method I came up with based on an answer to another question in the kendo forums. It uses datejs, but could easily be adapted to work without.
/// <reference path="../../typings/datejs/datejs.d.ts" />
declare module kendo.ui
{
class AgendaView implements kendo.ui.SchedulerView
{
startDate(): IDateJS;
endDate(): IDateJS;
}
}
class MonthlyAgendaView extends kendo.ui.AgendaView
{
endDate(): IDateJS
{
var date = this.startDate().clone();
date.moveToLastDayOfMonth();
return date;
}
}

Marionette - Data across multiple views with controllers

I am having some trouble figuring out how to setup "controller" wide data. Here is my controller code:
Mod.Controller = Marionette.Controller.extend({
region: App.layout.mainRegion,
initialize: function(){
var self = this;
this.layout = new SomeLayout();
this.collection = new SomeCollection();
$.when(this.collection.fetch()).done(function(){
self.region.show(self.layout);
});
},
index: function(opts){
var v = new Mod.ViewOne();
this.layout.mainRegion.show(v);
},
overview: function(opts){
var v = new Mod.ViewTwo();
this.layout.mainRegion.show(v);
},
onClose: function(){
this.layout.close();
}
});
The problem is, the view functions (index and overview) are being called before the collection has finished fetching, so the layout is being displayed empty.
I'd rather not have to fetch the data in each view function, since it'll hit the database unnecessary. Short of having some nasty if statement boilerplate in each view function, is there any other way I can achieve this?
The problem is your routes are being triggered before your collection is fully fetched.
I can think of two ways to get around this,
Options 1: Don't start your router until your collection is fetched.
this will work if this is your only controller, if you have other controller this solution could become more complicated.
initialize: function(){
var self = this;
this.layout = new SomeLayout();
this.collection = new SomeCollection();
$.when(this.collection.fetch()).done(function(){
self.region.show(self.layout);
Backbone.history.start();
});
},
Options 2: Make your routes aware of the fetch call
you could store the promise from the collection.fetch call and use that in your controller functions. I prefer this method since it's more flexible.
initialize: function(){
var self = this;
this.layout = new SomeLayout();
this.collection = new SomeCollection();
this.collectionPromise = this.collection.fetch();
this.collectionPromise.done(function(){
self.region.show(self.layout);
});
},
index: function(opts){
var self = this;
this.collectionPromise.done(function(){
var v = new Mod.ViewOne();
self.layout.mainRegion.show(v);
});
}

Backbone.js: How to call methods on the collection within an object literal

I have the following backbone.js code. I'm using an object literal for organizing my code, which has left me with a question regarding the best way to proceed. The application (in its simplified form below) has a control panel (which can be shown or hidden) which is used to add new categories to a collection. (Question follows)
(function($){
// ============================= NAMESPACE ========================================
var categoryManager = categoryManager || {};
// ============================= APPLICATION =================================================
categoryManager.app = categoryManager.app || {
/* Used to Initialise application*/
init: function(){
//this.addView = new this.addCategoryView({el: $("#add-new-category")})
//this.collection = new this.categoryCollection();
new this.addCategoryView({el: $("#add-new-category")})
new this.categoryCollection();
},
categoryModel: Backbone.Model.extend({
name: null
}),
addCategoryView: Backbone.View.extend({
events: {
"click #add-new-category-button.add" : "showPanel",
"click #add-new-category-button.cancel" : "hidePanel",
"click #new-category-save-category" : "addCategory"
},
showPanel: function() {
$('#add-new-category-button').toggleClass('add').toggleClass('cancel');
$('#add-new-category-panel').slideDown('fast');
},
hidePanel: function() {
$('#add-new-category-button').toggleClass('add').toggleClass('cancel');
$('#add-new-category-panel').stop().slideUp('fast');
},
addCategory: function() {
//categoryManager.app.collection.create({
categoryManager.app.categoryCollection.create({ // My Problem is with this line
name: $('#name').val()
});
}
}),
categoryCollection: Backbone.Collection.extend({
model: this.categoryModel,
initialize: function () {
}
})
}
// ============================= END APPLICATION =============================================
/* init Backbone */
categoryManager.app.init();
})(jQuery);
Now obviously the problem with the above, is that calling the addCategory function tries to call a function on an object which is uninitialized. I've worked round the problem (see commented out code) by calling the function instead on a object which is instantiated within the init function. My question is - is this the right thing to do? I detect a code smell. I feel that the contents of the object literal shouldn't rely on the object being created in order to be valid. the function addCategory in this instance wouldn't work unless the init function had been called on the parent first. Is there another pattern here that I should be using?
How else would I pass the contents of the 'create new category form' to the collection in order to be added (I'm using create because I want to automatically validate/create/persist the model and It seems like the easiest thing to do). I'm a rock bottom novice with backbone (this is my 'hello world')
Thanks
I think the main issue is you are treating categoryCollection as if it's an object. It's not really an object, but a constructor function. So first you need to create an instance, as you have discovered.
Then the addCategoryView needs some way of referencing the instance. It looks like you don't have a model associated with the view. I would suggest creating a model and storing the categoryCollection instance as a property of the model. Something like this (warning, untested code):
var model = new BackBone.Model({
categories: new categoryManager.app.CategoryCollection()
});
var view = new categoryManager.app.AddCategoryView({
el: $("#add-new-category"),
model: model
});
Then you can just use this.model.categories from inside addCategoryView.
As an aside, a common Javascript convention is to capitalize the names of constructors. Calling the constructor CategoryCollection might make the code a little bit clearer.
You need to initialize collection before create a new instance of a model
addCategory: function() {
var collection = categoryManager.app.categoryCollection;
!collection.create && (collection = new collection);
collection.create({
name: $('#name').val()
});
}

Resources