Small Jasmine test for Javascript file: Best approach to test it - backbone.js

First of all I want to say that I am new in Jasmine, so I beg for your kind comprehension if the question is very basic. I am writing a test for this file:
define([
'q',
'backbone',
'marionette',
'education/eet/views/destinationview',
'education/eet/views/editdestinationview',
'education/eet/models/destination',
'common/ajaxerrorhandler',
'common/alertdialog'
], function (Q, Backbone, Marionette, DestinationView, EditDestinationView, Destination, AjaxErrorHandler, AlertDialog) {
'use strict';
var ReferenceDataController = Marionette.Controller.extend({
initialize: function (options) {
this._subjectCompositeId = options.subjectCompositeId;
},
getView: function (destinationTypes, editMode) {
var self = this,
deferred = Q.defer(),
destination = new Destination();
destination.fetch({
data: {subjectCompositeId: self._subjectCompositeId}
}).done(function () {
var view;
if (editMode) {
view = new EditDestinationView({
model: destination,
'destinationTypes': destinationTypes
});
view.on('click:saveDestination', self._handleSaveDestination, view);
} else {
view = new DestinationView({
model: destination
});
}
deferred.resolve(view);
}).fail(function (jqXHR) {
deferred.reject(jqXHR);
});
return deferred.promise;
},
_handleSaveDestination: function () {
if (this.model.isValid(true)) {
this.model.save(null, {
success: function () {
Backbone.Wreqr.radio.vent.trigger('education', 'show:destination');
},
error: function (jqXHR) {
var userFriendlyErrorString = AjaxErrorHandler.buildDefaultErrorMessage(jqXHR);
return new AlertDialog(userFriendlyErrorString);
}
});
}
}
});
return ReferenceDataController;
});
The problem is that I am not very sure about how can I access the variables inside it to test it. I am a Java Tester but never test Javascript even when I wrote, so I am very confused with it.
Any hint or code will be actually appreciated.
Thanks.

Think of Jasmine suite/spec as your application that is dependent on this module.
We do our specs as RequireJS modules that require the appropriate module, instantiate it - sometimes on module level, sometimes on suite (describe) level, sometimes on spec (it) level.
At this point, due to you (in it) having an access to an actual instance of the class, you invoke its various methods and test for the results using various asserts in the form of
expect(something).toBeTruthy();
or similar.

Related

Backbone error: Model is not a constructor

Afternoon all, I'm relatively new to backbone and have been stumped for 3 days with this error which I have not seen before.
I have a collection 'TestCollection' which defines it's model as a function. When the collection is loaded I get an error the first time it attempts to make a model with class 'TestModel'.
The error I get is:
Uncaught TypeError: TestModel is not a constructor
at new model (testCollection.js:14)
at child._prepareModel (backbone.js:913)
at child.set (backbone.js:700)
at child.add (backbone.js:632)
at child.reset (backbone.js:764)
at Object.options.success (backbone.js:860)
at fire (jquery.js:3143)
at Object.fireWith [as resolveWith] (jquery.js:3255)
at done (jquery.js:9309)
at XMLHttpRequest.callback (jquery.js:9713)
I believe I have given both the collection and the model all of the code they should need to work. It feels like something has gone wrong with the loading, but when I put a console.log at the top of the model file I could see that it is definitely being loaded before the collection attempts to use it.
Any help would be massively appreciated.
TestCollection:
define([
'backbone',
'models/testModel'
], function(Backbone, TestModel) {
var TestCollection = Backbone.Collection.extend({
model: function(attrs) {
switch (attrs._type) {
case 'test':
console.log('making a test model')
return new TestModel();
}
},
initialize : function(models, options){
this.url = options.url;
this._type = options._type;
this.fetch({reset:true});
}
});
return TestCollection;
});
TestModel:
require([
'./testParentModel'
], function(TestParentModel) {
var TestModel = TestParentModel.extend({
urlRoot: 'root/url',
initialize: function() {
console.log('making test model')
}
});
return TestModel;
});
File where TestCollection is made:
define(function(require) {
var MyProjectCollection = require('collections/myProjectCollection');
var TestCollection = require('collections/testCollection');
Origin.on('router:dashboard', function(location, subLocation, action) {
Origin.on('dashboard:loaded', function (options) {
switch (options.type) {
case 'all':
var myProjectCollection = new MyProjectCollection;
myProjectCollection.fetch({
success: function() {
myProjectCollection.each(function(project) {
this.project[project.id] = {};
this.project[project.id].testObjects = new TestCollection([], {
url: 'url/' + project.id,
_type: 'test'
});
});
}
});
}
});
});
I've had a look around stack overflow, it does not appear to be the issue below (which seems to be the most common issue).
Model is not a constructor-Backbone
I also do not think I have any circular dependencies.
Any help would be massively appreciated as I am completely stumped. I've tried to include only the relevant code, please let me know if additional code would be useful.
Thanks
I can't say for other parts of the code but an obvious problem you have is misunderstanding what data is passed to the model creator function.
var TestCollection = Backbone.Collection.extend({
model: function(attrs) {
switch (attrs._type) { // attrs._type does exist here
console.log( attrs ); // Will print { foo: 'bar' }
case 'test': // This will always be false since attrs._type does not exist
console.log('making a test model')
return new TestModel();
default:
return new Backbone.Model(); // Or return some other model instance,
// you MUST have this function return
// some kind of a Backbone.Model
}
},
initialize : function(models, options){
this.url = options.url;
this._type = options._type;
this.fetch({reset:true});
}
});
new TestCollection([ { foo: 'bar' }], {
url: 'url/' + project.id,
_type: 'test' // This will NOT be passed to the model attrs, these are
// options used for creating the Collection instance.
})
To re-iterate. When you instantiate a Collection you pass an array of plain objects [{ foo: 'bar'}, { foo: 'baz'}] ( or you get them via fetch like you're doing ). That object will be passed as the attrs parameter in the model function, and the model function MUST return at least some kind of a Backbone.Model instance so you need a fallback for your switch statement.

Unit testing promises in js-data-angular models

We use js-data and js-data-angular in our project.
I have the following model:
(function () {
'use strict';
angular.module('dash.models')
.factory('Diagnosis', ['DS', function (DS) {
function transform(resourcename, attrs, cb) {
attrs.icd9codes.forEach(function (el) {
delete el.add;
});
cb(null, attrs);
}
this.transform = transform;
return DS.defineResource({
name: 'diagnosis',
idAttribute: 'id',
endpoint: '/diagnosis',
baseUrl: '/api',
beforeCreate: transform,
beforeUpdate: transform
});
}]);
}());
And the following call to said model:
var startEditing = self.startEditing = function(parentScope, diagnosis) {
Diagnosis.findAll({
deep:true
}, {
endpoint: '/diagnosis/' + diagnosis.id
}).then(function(d) {
$scope.diagnosis = d;
$scope.inScope = true;
});
};
In my unit test, I mock the call like this:
var diagDeferred = _$q_.defer();
diagDeferred.resolve({
'name': 'Breast',
'categories': null,
'id': '026c7cd0-14ef-4312-a8f1-2092107b0e50',
'icd9codes': [{id: '1', code: '001', description: 'ICD9 Code'}]
});
spyOn(Diagnosis, 'findAll').and.returnValue(diagDeferred.promise);
And the actual call is mocked, what doesn't get executed (and I can't find any reliable information on how to get this done) is the function inside the .then of the Diagnosis.findAll
I know the code works, but I need to cover it with unit tests and I'm coming up dry.
Thanks.
I think you forgot to call $scope.digest() in your test. Here is a working fiddle.
After you call startEditing(), you should call $scope.$digest() so that your mock promise is executed and you can get your data in then block. Hope it helps.

AngularJS - What would this look like had it been created TDD style?

I'm in the process of transferring all of our code onto Karma and Jasmine and am having a hard time figuring out where I start.
What would this code look like had I started building it from a TDD standpoint? What does a simple test look like?
Note: This code works 100%, but I don't have any tests setup.
(function() {
"use strict";
angular.module('system_centers', [
'system'
])
.factory('System', ['Api', function(Api) {
this.loadSystem = function(contactId, cardId) {
return Api.get('lmc/contact/system/' + contactId, {
card_id: cardId
});
};
this.completeSystem = function(recordId) {
return Api.put('system/complete/' + recordId);
};
this.createSystem = function(contactId, cardId) {
if (+contactId === 0 || +cardId === 0) {
return false;
}
return Api.post('contact/system/' + contactId, {
card_id: cardId,
type: 'systems',
origin: 'lmc'
});
};
return this;
}])
.controller('System_centersCtrl', ['$scope', 'System', function($scope, System) {
$scope.main.cardType = 'systems';
$scope.main.type = 'system_centers';
$scope.completeSystem = function(recordId) {
System.completeSystem(recordId).success(function(){
toastr.success("System completed!");
$scope.createSystem();
$scope.loadSystems();
});
};
$scope.createSystem = function() {
System.createSystem($scope.main.contactId, $scope.main.cardId).success(function() {
$scope.loadSystem($scope.main.contactId, $scope.main.cardId);
$scope.loadContacts();
});
};
$scope.loadSystem = function() {
System.loadSystem($scope.main.contactId, $scope.main.cardId).success(function(data) {
if (data.error) {
$scope.createSystem();
} else {
$scope.main.record = data.record;
}
});
};
$scope.loadSystems();
}]);
})();
Testing is easy, you just need to assert that your factory is working correctly. This doesn't mean that you want actually get/put/post stuff, that belongs to the Api test. Here we just want to know that calling certain functions of our factory will call some Api functions with the correct parameters.
I imagine that Api belongs to the system module. I load it and mock it:
beforeEach(module('system', function($provide) {
api = {
get: function(url, params) {},
put: function(url, params) {},
post: function(url, params) {}
};
spyOn(api, 'get');
spyOn(api, 'put');
spyOn(api, 'post');
$provide.value('Api', api);
}));
module will load your system module and then we just need to create a simple object with the interface of our Api service. No need to implement anything on them.
Then we just need to spy the methods (to be able to assert that they have been called).
Next, we load the system_centers module and we inject our services:
beforeEach(module('system_centers'));
beforeEach(inject(function(System) {
system = System;
}));
inject is used to inject dependencies in our tests. We just need to inject our System factory.
What rest are the test, I created a bunch of them:
it('should load the system', function() {
system.loadSystem(1, 0);
expect(api.get).toHaveBeenCalledWith('lmc/contact/system/1', {card_id : 0});
});
it('should be able to complete the system', function() {
system.completeSystem(20);
expect(api.put).toHaveBeenCalledWith('system/complete/20');
});
it('should create the system', function() {
system.createSystem(1, 3);
expect(api.post).toHaveBeenCalledWith('contact/system/1', { card_id: 3, type: 'systems', origin: 'lmc'});
});
it('should not create the system if contact_id is 0', function() {
system.createSystem(0, 20);
expect(api.post).not.toHaveBeenCalled();
});
it('should not create the system if card_id is 0', function() {
system.createSystem(1, 0);
expect(api.post).not.toHaveBeenCalled();
});
They are much the same. We call some factory method and we expect that our Api has been called with some parameters. Or even that calling createSystem with contact or card id with 0 won't call the Api.
Well, this is a good head start. You can continue with more tests or with other parts of your application.
Here is the plunker: http://plnkr.co/edit/5vfg0Y1G0vo2nnz0xByN?p=preview

backbone extension file loads, some helpers work, one doesn't

I have a backbone-extend.js file that I load in the require define in app.js. It has a Backbone.View extender class defining a couple helper methods. Two of the methods work just fine in my views, one always errors with Uncaught TypeError: Object [object global] has no method 'gotoUrl'. Why would just this one method be not defined but the other two are working fine? Do you see any issue in this code...
// Filename: backbone-extend.js
define([
'jquery',
'underscore',
'backbone'
], function($, _, Backbone) {
var helpers = {
eventSyncError: function(model,response,options) {
console.log('Sync error='+response.statusText);
$('#server-message').css({'color':'red', 'font-weight':'bold'}).text(response.statusText);
},
gotoUrl: function(url,delay) {
var to = setTimeout(function() { Backbone.history.navigate(url, true); }, delay);
},
getFormData: function(form) {
var unindexed_array = form.serializeArray();
var indexed_array = {};
$.map(unindexed_array, function(n, i) {
indexed_array[n['name']] = n['value'];
});
return indexed_array;
}
}
_.extend(Backbone.View.prototype, helpers);
});
Here is the code in view that calls it...
eventSyncMemberSaved: function(model,response,options) {
console.log("Member saved!");
$('#server-message').css({'color':'green', 'font-weight':'bold'}).text("Member saved!");
this.gotoUrl('members',2000);
//setTimeout(function() { Backbone.history.navigate('members', true); }, 2000);
},
saveMember: function() {
var data = this.getFormData($('#member-form'));
this.member.save(data, { success: this.eventSyncMemberSaved });
},
Thanks in advance for your help. I'm stuck.
The context of this is different in the success callback.
It no longer points to the view as it points to the xhr object
So it throws an error as that method is not available on the xhr object
To resolve it you need to bind the context of this to the success handler so that it points to the right object.
So in the initialize of the view add this code
initialize: function() {
_.bindAll(this, 'eventSyncMemberSaved');
}

Missing injection for test (unkown provider)

I am trying to write unit tests for my Angular.js application but I cannot manage to inject what I need (it is not able to find a suitable provider).
Does anyone see what I missed?
Firefox 21.0 (Linux) filter staticList should convert static list object into its display value FAILED
Error: Unknown provider: staticListProvider <- staticList in /path/to/my-app/public/third-party/angular/angular.js (line 2734)
createInjector/providerInjector<#/path/to/my-app/public/third-party/angular/angular.js:2734
getService#/path/to/my-app/public/third-party/angular/angular.js:2862
createInjector/instanceCache.$injector<#/path/to/my-app/public/third-party/angular/angular.js:2739
getService#/path/to/my-app/public/third-party/angular/angular.js:2862
invoke#/path/to/my-app/public/third-party/angular/angular.js:2880
workFn#/path/to/my-app/test/lib/angular/angular-mocks.js:1778
angular.mock.inject#/path/to/my-app/test/lib/angular/angular-mocks.js:1764
#/path/to/my-app/test/unit/filtersSpec.js:19
#/path/to/my-app/test/unit/filtersSpec.js:16
#/path/to/my-app/test/unit/filtersSpec.js:3
The application:
angular.module('myApp', ['myAppFilters', 'ui.bootstrap', '$strap.directives']).
// Some other stuff
The filters:
"use strict";
angular.module('myAppFilters', []).
filter('staticList', function () {
return function (listItem) {
if (!listItem) {
return '';
}
return listItem.value;
};
});
The test:
'use strict';
describe('filter', function () {
beforeEach(angular.module('myAppFilters'));
describe('staticList', function () {
it('should convert static list object into its display value',
inject(function (staticList) {
expect(undefined).toBe('');
expect({key: 'A', value: 'B'}).toBe('B');
}));
});
});
The Karma configuration:
basePath = '../';
files = [
JASMINE,
JASMINE_ADAPTER,
'public/third-party/jquery/*.js',
'public/third-party/angular/angular.js',
'public/third-party/angular/i18n/angular-*.js',
'public/third-party/moment/moment.min.js',
'public/third-party/moment/moment-*.js',
'public/js/**/*.js',
'test/lib/**/*.js',
'test/unit/**/*.js'
];
colors = true;
autoWatch = true;
browsers = ['Firefox'];
junitReporter = {
outputFile: 'test_out/unit.xml',
suite: 'unit'
};
If anybody wants to see the full code, the application repository is here: https://github.com/adericbourg/GestionCourrier
Thanks a lot,
Alban
In your inject code
it('should convert static list object into its display value',
inject(function (staticList) {
expect(undefined).toBe('');
expect({key: 'A', value: 'B'}).toBe('B');
}));
replace "inject(function (staticList)" with "inject(function (staticListFilter)". This is some random convention angular is following. You can check comments on this page for more info http://docs.angularjs.org/tutorial/step_09
I faced similar problem, but in my case my filter name contained suffix 'Filter' which caused the same injection problem.
.filter('assetLabelFilter', function(){
return function(assets, selectedLabels){
// Implementation here
};
});
I was finally able to solve the problem by manually injecting the filter to my test
'use strict';
describe('assetLabelFilter', function() {
beforeEach(module('filters.labels'));
var asset1 = {labels: ['A']};
var asset2 = {labels: ['B']};
var asset3 = {labels: []};
var asset4 = {labels: ['A', 'B']};
var assets = [asset1, asset2, asset3, asset4];
var assetLabelFilter;
beforeEach(inject(function($filter) {
assetLabelFilter = $filter('assetLabelFilter');
}));
it('should return only assets with selected label', function() {
var selectedLabels = ['B'];
expect(assetLabelFilter(assets, selectedLabels)).toEqual([asset2, asset4]);
});
});
The nice answer above made me realize that in order to use the angular tutorial way:
it('should ', inject(function(assetLabelFilter) {
var selectedLabels = ['B'];
expect(assetLabelFilter(assets, selectedLabels)).toEqual([asset2, asset4]);
}));
The filter name can't have the suffix 'Filter' because it is magic word as mentioned in the answer above.
To demonstrate the scenario I also created a Plunker

Resources