Unit testing promises in js-data-angular models - angularjs

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.

Related

Mongoose and Angular Populate Issue

I am new to mongoose and Angular and I am having an issue with mongoose's populate method. I have the following two mongoose schemas
var JobSchema = new mongoose.Schema({
jobName: String,
jobType: String,
status: String,
examples: [{type: mongoose.Schema.Types.ObjectId, ref: 'Example'}]
});
mongoose.model('Job', JobSchema);
and
var ExampleSchema = new mongoose.Schema({
content: String,
job: {type: mongoose.Schema.Types.ObjectId, ref: 'Job'}
});
mongoose.model('Example', ExampleSchema);
So basically the Job schema contains Example's. I also have the following Express route for getting the examples from a particular Job. I used this tutorial to figure out how to do this.
var Job = mongoose.model('Job');
var Example = mongoose.model('Example');
router.get('/jobs/:job', function (req, res) {
req.job.populate('examples', function (err, job) {
if (err) {return next(err);}
res.json(job);
});
});
Also, I am using the following to automatically retrieve the job from mongo and attach it to req.
router.param('job', function (req, res, next, id) {
var query = Job.findById(id);
query.exec(function (err, job) {
if (err) {
return next(err);
}
if (!job) {
return next(new Error('can\'t find job'));
}
req.job = job;
return next();
});
});
I also have the following Angular factory that uses this route
app.factory('jobs', ['$http', function ($http) {
var o = {
jobs: []
};
o.get = function (id) {
return $http.get('/jobs/' + id).then(function (res) {
return res.data;
});
};
return o;
}]);
I also created the following state which is supposed to immediately populate the examples for a given Job id using the above factory.
.state('jobs', {
url: '/jobs/{id}',
templateUrl: '/jobs.html',
controller: 'NerCtrl',
resolve: {
post: ['$stateParams', 'jobs', function ($stateParams, jobs) {
return jobs.get($stateParams.id);
}]
}
});
The problem comes when I actually try to show the examples using a controller.
app.controller('NerCtrl', [
'$scope',
'job',
function ($scope, job) {
$scope.examples = job.examples;
}]);
The view that tries to use $scope.examples just displays {{examples}} rather than the actual content of the scope variable. In fact, nothing in the controller seems to work with the `job` injection (not even simple 'alerts').
It looks the problem comes from the `job` injection in the controller. This is supposed to refer to the job that is retrieved in the resolve given the id but it doesn't look like this is working.
In addition, I have curled an example record's url (eg. curl http://localhost:3000/jobs/56920a1329cda48f16fc0815) and it does return the desired Job record, so it does look like the route part is working correctly. I suspect the problem is somewhere in the 'resolve' or the way in which I am injecting the result of the resolve into the controller.
Ok this was a silly mistake. The post inside the Job state should have been job. i.e.
.state('jobs', {
url: '/jobs/{id}',
templateUrl: '/jobs.html',
controller: 'NerCtrl',
resolve: {
job: ['$stateParams', 'jobs', function ($stateParams, jobs) {
return jobs.get($stateParams.id);
}]
}
});
In my inexperience, I did not know what post was referring to, but I suppose it refers to the job that is returned from jobs.get($stateParams.id) which is then the name that gets injected in the controller. So obviously the name in resolve must be consistent with what is injected in the controller.

Small Jasmine test for Javascript file: Best approach to test it

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.

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

Proper place for data-saving logic in AngularJS

App design question. I have a project which has a very large number of highly customized inputs. Each input is implemented as a directive (and Angular has made this an absolute joy to develop).
The inputs save their data upon blur, so there's no form to submit. That's been working great.
Each input has an attribute called "saveable" which drives another directive which is shared by all these input types. the Saveable directive uses a $resource to post data back to the API.
My question is, should this logic be in a directive at all? I initially put it there because I thought I would need the saving logic in multiple controllers, but it turns out they're really happening in the same one. Also, I read somewhere (lost the reference) that the directive is a bad place to put API logic.
Additionally, I need to introduce unit testing for this saving logic soon, and testing controllers seems much more straightforward than testing directives.
Thanks in advance; Angular's documentation may be… iffy… but the folks in the community are mega-rad.
[edit] a non-functional, simplified look at what I'm doing:
<input ng-model="question.value" some-input-type-directive saveable ng-blur="saveModel(question)">
.directive('saveable', ['savingService', function(savingService) {
return {
restrict: 'A',
link: function(scope) {
scope.saveModel = function(question) {
savingService.somethingOrOther.save(
{id: question.id, answer: question.value},
function(response, getResponseHeaders) {
// a bunch of post-processing
}
);
}
}
}
}])
No, I don't think the directive should be calling $http. I would create a service (using the factory in Angular) OR (preferably) a model. When it is in a model, I prefer to use the $resource service to define my model "classes". Then, I abstract the $http/REST code into a nice, active model.
The typical answer for this is that you should use a service for this purpose. Here's some general information about this: http://docs.angularjs.org/guide/dev_guide.services.understanding_services
Here is a plunk with code modeled after your own starting example:
Example code:
var app = angular.module('savingServiceDemo', []);
app.service('savingService', function() {
return {
somethingOrOther: {
save: function(obj, callback) {
console.log('Saved:');
console.dir(obj);
callback(obj, {});
}
}
};
});
app.directive('saveable', ['savingService', function(savingService) {
return {
restrict: 'A',
link: function(scope) {
scope.saveModel = function(question) {
savingService.somethingOrOther.save(
{
id: question.id,
answer: question.value
},
function(response, getResponseHeaders) {
// a bunch of post-processing
}
);
}
}
};
}]);
app.controller('questionController', ['$scope', function($scope) {
$scope.question = {
question: 'What kind of AngularJS object should you create to contain data access or network communication logic?',
id: 1,
value: ''
};
}]);
The relevant HTML markup:
<body ng-controller="questionController">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="saveModel(question)" />
</body>
An alternative using only factory and the existing ngResource service:
However, you could also utilize factory and ngResource in a way that would let you reuse some of the common "saving logic", while still giving you the ability to provide variation for distinct types of objects / data that you wish to save or query. And, this way still results in just a single instantiation of the saver for your specific object type.
Example using MongoLab collections
I've done something like this to make it easier to use MongoLab collections.
Here's a plunk.
The gist of the idea is this snippet:
var dbUrl = "https://api.mongolab.com/api/1/databases/YOURDB/collections";
var apiKey = "YOUR API KEY";
var collections = [
"user",
"question",
"like"
];
for(var i = 0; i < collections.length; i++) {
var collectionName = collections[i];
app.factory(collectionName, ['$resource', function($resource) {
var resourceConstructor = createResource($resource, dbUrl, collectionName, apiKey);
var svc = new resourceConstructor();
// modify behavior if you want to override defaults
return svc;
}]);
}
Notes:
dbUrl and apiKey would be, of course, specific to your own MongoLab info
The array in this case is a group of distinct collections that you want individual ngResource-derived instances of
There is a createResource function defined (which you can see in the plunk and in the code below) that actually handles creating a constructor with an ngResource prototype.
If you wanted, you could modify the svc instance to vary its behavior by collection type
When you blur the input field, this will invoke the dummy consoleLog function and just write some debug info to the console for illustration purposes.
This also prints the number of times the createResource function itself was called, as a way to demonstrate that, even though there are actually two controllers, questionController and questionController2 asking for the same injections, the factories get called only 3 times in total.
Note: updateSafe is a function I like to use with MongoLab that allows you to apply a partial update, basically a PATCH. Otherwise, if you only send a few properties, the entire document will get overwritten with ONLY those properties! No good!
Full code:
HTML:
<body>
<div ng-controller="questionController">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="save(question)" />
</div>
<div ng-controller="questionController2">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="save(question)" />
</div>
</body>
JavaScript:
(function() {
var app = angular.module('savingServiceDemo', ['ngResource']);
var numberOfTimesCreateResourceGetsInvokedShouldStopAt3 = 0;
function createResource(resourceService, resourcePath, resourceName, apiKey) {
numberOfTimesCreateResourceGetsInvokedShouldStopAt3++;
var resource = resourceService(resourcePath + '/' + resourceName + '/:id',
{
apiKey: apiKey
},
{
update:
{
method: 'PUT'
}
}
);
resource.prototype.consoleLog = function (val, cb) {
console.log("The numberOfTimesCreateResourceGetsInvokedShouldStopAt3 counter is at: " + numberOfTimesCreateResourceGetsInvokedShouldStopAt3);
console.log('Logging:');
console.log(val);
console.log('this =');
console.log(this);
if (cb) {
cb();
}
};
resource.prototype.update = function (cb) {
return resource.update({
id: this._id.$oid
},
angular.extend({}, this, {
_id: undefined
}), cb);
};
resource.prototype.updateSafe = function (patch, cb) {
resource.get({id:this._id.$oid}, function(obj) {
for(var prop in patch) {
obj[prop] = patch[prop];
}
obj.update(cb);
});
};
resource.prototype.destroy = function (cb) {
return resource.remove({
id: this._id.$oid
}, cb);
};
return resource;
}
var dbUrl = "https://api.mongolab.com/api/1/databases/YOURDB/collections";
var apiKey = "YOUR API KEY";
var collections = [
"user",
"question",
"like"
];
for(var i = 0; i < collections.length; i++) {
var collectionName = collections[i];
app.factory(collectionName, ['$resource', function($resource) {
var resourceConstructor = createResource($resource, dbUrl, collectionName, apiKey);
var svc = new resourceConstructor();
// modify behavior if you want to override defaults
return svc;
}]);
}
app.controller('questionController', ['$scope', 'user', 'question', 'like',
function($scope, user, question, like) {
$scope.question = {
question: 'What kind of AngularJS object should you create to contain data access or network communication logic?',
id: 1,
value: ''
};
$scope.save = function(obj) {
question.consoleLog(obj, function() {
console.log('And, I got called back');
});
};
}]);
app.controller('questionController2', ['$scope', 'user', 'question', 'like',
function($scope, user, question, like) {
$scope.question = {
question: 'What is the coolest JS framework of them all?',
id: 1,
value: ''
};
$scope.save = function(obj) {
question.consoleLog(obj, function() {
console.log('You better have said AngularJS');
});
};
}]);
})();
In general, things related to the UI belong in a directive, things related to the binding of input and output (either from the user or from the server) belong in a controller, and things related to the business/application logic belong in a service (of some variety). I've found this separation leads to very clean code for my part.

Angular resource (ngResource) won't make the AJAX call when called inside a function

I'm trying to make an AJAX call with ngResource, In the code below 'a' and 'b' both print, but the AJAX call from Table.import() does not get made. If I move the AJAX call outside of onFileRead, then it works. What could be the problem?
var TableImportController = ['$scope','Table', 'project', 'table',
function($scope, Table, project, table) {
$scope.table = table;
$scope.project = project;
$scope.onFileRead = function(file) {
console.log('a');
Table.import({ data : file.data}, function() {
}, function() {
});
console.log('b');
};
}];
Where Table is an ngResource
.factory('Table', function($resource) {
var Table = $resource('/api/tables/:id:listAction/:itemAction',
{
id: '#id',
listAction: '#listAction',
itemAction: '#itemAction'
},
{
update: { method: 'PUT' },
import : { method: 'POST', params: { listAction: 'import' }},
}
);
return Table;
});
You are declaring $scope.onFileRead as a function.
What is calling onFileRead?
When you move the call outside of the function, it is being run as part of initialization.
What provides the input file?
Probably bind to the onFileRead function from something in your DOM.
I figured it out. It looks like I ran into this bug: https://github.com/angular/angular.js/issues/2794#issuecomment-18807158.
I solved the issue by wrapping the AJAX call (and eventually moved it to where the onFileRead callback is triggered) in a scope.$apply(function() { });

Resources