Testing an angular service - angularjs

This might be a really silly question but I couldn't find an answer anywhere else.
I want to unit test a service that for now has two simple operations:
angular.module('transaction').factory('Transaction',
function() {
var transactionList = [];
// Public API
return {
addTransaction: function(transaction) {
transactionList.push(transaction);
},
getTransactions: function(){
return transactionList;
}
};
}
);
Tests
Now in my tests I want to test if the service's addTransaction and getTransactions methods work, but I'm not really sure how to do it properly, because what I'm doing right now is using the other method to test the first, e.g:
it('should be able to return an array of transactions', function() {
//add new transaction
var trans = {id: 1, value: test};
Transaction.addTransaction(trans);
//test get
var result = Transaction.getTransactions();
expect(Array.isArray(result)).toBe(true);
expect(result).toEqual([trans]);
});
it('should be able to add a transaction', function(){
var trans = {id: 1, value: test};
Transaction.addTransaction(trans);
var result = Transaction.getTransactions();
expect(result).toEqual([trans]);
});
I expect these methods to become more complex, but what I wanted to do would be to have a way to test one of the methods without having to use the other. It would be great if anyone could point me to an example of some tests for operations similar to this.

I can see two ways for testing these independently:
1) make transactionList publicly available:
Transaction.addTransaction(trans);
expect(Transaction.transactionList).toEqual([trans]);
2) return transactionList in both methods
expect(Transaction.addTransaction(trans)).toEqual([trans]);
I would go with the second solution, as you can keep transactionList private.

Related

Jasmine: Testing a function returning a promise

I am writing tests using Jasmine for my angular application. All the tests are passing. My class looks like follows:
class xyz implements ng.IComponentController {
private myList: ng.IPromise<MyList[]> ;
//declare necessary variables
/* #ngInject */
constructor(private ListService: ListService,
) {
this.myList = this.ListService.getList();
}
public onChange(): void {
this.isNameUnique(this.name).then(function(unique){
scope.isUnique = unique;
scope.errorNameInput = !reg.test(scope.name) || !scope.isUnique;
scope.myFunction({
//do something
});
});
}
public isNameUnique(name: string): ng.IPromise<boolean> {
return this.myList
.then(
(names) => {
_.mapValues(names, function(name){
return name.uuid.toLowerCase();
});
return (_.findIndex(names, { uuid : uuid.toLowerCase() }) === -1) ? true : false;
});
}
}
Here, I am using ListService to pre-populate my list in the constructor itself (so it calls the service only once). Then, in my onChange method, I am checking
if a name is unique or not. The isNameUnique is returning a boolean promise.
Now, I'm trying to get 100% coverage for my test. I'm getting confused about testing isNameUnique method here. My first test is:
(Assuming myList is a json similar to response I will get from service)
this.$scope.myFunction = jasmine.createSpy('myFunction');
it('should ...', function() {
this.view.find(NAME_INPUT).val('blue').change(); // my view element.
this.getList.resolve(myList);
this.controller.isNameUnique('blue').then(function (unique) {
expect(unique).toEqual(false); //since blue is already in my json
expect(this.controller.errorNameInput).toEqual(true); //since its not unique, errornameinput will be set to true
expect(this.$scope.myFunction).toHaveBeenCalled();
});
});
I would expect this test to cover the line: scope.errorNameInput = !reg.test(scope.name) || !scope.isUnique and invocation of myFunction() but it still shows uncovered. Not sure why.
Please let me know if you see anything else wrong since I'm quite new to Angular and Jasmine. Thanks.
You need to call $scope.$digest() to cause your promise to resolve in your test. There is a handy tutorial that discusses this in depth here
Hope that helps!

Jaydata saveChanges() counts tracked / changed entities, but doesn't send a batch request (with OData v4 Provider and Web Api)

by working with jaydata i am adding entities to a tree structure with deep nesting of entity-objects.
I attach the upper entity to the context and edit/add/remove related child entities. At the end i use saveChanges() with a promise.
The count-value passed to the promise tells that all changed entities have been counted correctly but saveChanges() didn't execute a batch request, persisting these entities.
So it feels like nothing else happens, but counting entities.
I post a small code example. I am quite sure, that the references of the entites are set correctly. (Working with jaydata, odata v4, web api and angularjs)
Is someone else having this problem with jaydata and found the reason?
Thanks for your help. =)
Greetings Paul
// Attach upper entity
DataService.jaydata.attach(viewModel.currentSkillTree.entity);
// Generating new entities
var newSkill = new DataService.jaydata.Skills.elementType({
Id: undefined,
Name: 'New skill',
Levels: [],
IconId: 47,
SkillTreeUsage: []
});
var newSkillLevel = new DataService.jaydata.SkillLevels.elementType({
Id: undefined,
ShortTitle: 'New level',
Skill: newSkill,
SkillId: undefined,
Level: 1,
RequirementSets: []
});
var newRequirementSet = new DataService.jaydata.RequirementSets.elementType({
Id: undefined,
SkillLevel: newSkillLevel,
SkillLevelId: undefined,
SkillTree: undefined,
SkillTreeId: viewModel.currentSkillTree.entity.Id,
});
var newSkillTreeElement = new DataService.jaydata.SkillTreeElements.elementType({
Id: undefined,
SkillTree: undefined,
SkillTreeId: viewModel.currentSkillTree.entity.Id,
Skill: newSkill,
SkillId: undefined,
Position: { X: x, Y: y }
});
// Completing object-references
viewModel.currentSkillTree.entity.Elements.push(newSkillTreeElement);
newSkill.Levels.push(newSkillLevel);
newSkill.SkillTreeUsage.push(newSkillTreeElement)
newSkillLevel.RequirementSets.push(newRequirementSet);
// Saving
DataService.jaydata.saveChanges()
.then(function (cnt) {
console.log('Saved entities:', cnt);
// The cnt-result in console is 4
// But no request was executed, nothing was saved
}, function (exception) {
console.log(exception); // Also no exception was thrown
});
So to not be that unkind.
The solution to solve the problem above to me, since i tried nearly every combination with entities (adding, attaching, .save(), .saveChanges(), object-references etc, figuring out it doesn't make sense anyway, it just acted the same way and seems to be so buggy), ended up within a workaround acting with classic nested async calls.
The solution was to save entities seperately within nested promises and to turn off the batch behavior of jaydata, to avoid double requests.
You can find the option within $data.defaults
$data.defaults.OData.disableBatch = true;
As result i am dealing now with good old nasty pyramids of doom, which at least gave the possibility back to save entities in the right order, with full control, the way the api needs it.
// Saving new SkillLevelRequirement connection
if (isConnectionGiven === false) {
// The first level of source skill where the target-skill-requirement will be added
var sourceSkillLevel = Enumerable
.From(sourceSkill.Levels)
.FirstOrDefault(null, function (x) {
return x.Level === 1;
});
// The last level of the target-skill to solve
var targetSkillLevel = Enumerable
.From(targetSkill.Levels)
.FirstOrDefault(null, function (x) {
return x.Level === targetSkill.Levels.length;
});
// First set of first level from source skill (will be used to add skilllevel-requirement)
var firstRequirementSet = sourceSkillLevel.RequirementSets[0];
// New RequirementAsignment
var newRequirementAssignment = new DataService.jaydata.RequirementAssignments.elementType({
RequirementSetId: firstRequirementSet.Id,
Order: 1
});
// New Requirement
var newRequirement = new DataService.jaydata.Requirements.elementType({
Title: requirementTypes.SKILL_CONNECTION,
RequirementOfIntId: undefined,
RequirementOfBoolId: undefined,
RequirementOfSkillLevelId: 0
});
// New RequirementOfSkillLevel
var newRequirementOfSkillLevel = new DataService.jaydata.RequirementsOfSkillLevel.elementType({
SkillLevelId: targetSkillLevel.Id,
});
// Loading symbol
showBusyIndicator();
newRequirementOfSkillLevel.save()
.then(function () {
newRequirement.RequirementOfSkillLevelId = newRequirementOfSkillLevel.Id;
newRequirement.save()
.then(function () {
newRequirementAssignment.RequirementId = newRequirement.Id;
newRequirementAssignment.save()
.then(function () {
// Loading symbol will be closed after tree reloaded
reloadCurrentTree();
}, function (exception) {
showJayDataExceptionModal(exception);
});
}, function (exception) {
showJayDataExceptionModal(exception);
});
}, function (exception) {
showJayDataExceptionModal(exception);
});
}
}
#jaydata developers: Thanks for 42 new grey hairs. I'm still at the point where i think i am using your tool wrong and jaydata could do so much better. Better up your documentation, sieriously. No desserts for you today.

Can you chain properties?

I have an automation test that I have switched out most variables for properties which has been working fantastic for me unless i need to chain something. Here is an example of what I'd like it to look like:
var test = module.exports = {
outerElement: element(by.cssContainingText('some.div' 'A name'),
innerElement: $('something.else'),
clickOnaName: function () {
this.outerElement.this.innerElement.click();
},
However I have to use this code because chaining doesn't work the way I am using it:
var outerElement = element(by.cssContainingText('some.div'
'A name');
var innerElement = $('something.else');
var test = module.exports = {
clickOnaName: function() {
outerElement.innerElement.click();
},
Is there a way for me to do chain or should i just leave those elements as variables
please let me know if this helps!
You can use .element(el.locator()) to extend the elements. You can use multiple selectors at once. You can also hit arrays of elements.
Keep in mind that this kind of chaining of protractor selectors is the same as a css space child selector, and not as a > selector.
I.e. $('.parent').$('.child') will select the same elements as in a css file .parent .child, getting ALL children and not just direct children.
module.exports = function(){
this.parent = $('.parent');
this.child = $('.child');
this.childOfParent = parent.element(child.locator());
this.directParentChild = $('.parent').$('.child');
this.parentArray = $$('.parents');
this.child = $('.child');
this.children = parentArray.get(2).element(child.locator());
}
Adding in clicks and such should be pretty straightforward from there, page.childOfParent.click for example.

Modeling relational data from REST api via angularjs

I'm building an app, that is backed with node-mysql combo, and angularjs on the frontend part. The backend REST service is ready, but I'm struggling with modeling my relational data. There are some questions regarding this like : $resource relations in Angular.js or $resource relations in Angular.js [updated] . Are those approaches still the best approaches, or were there any significant changes in $resource ? Or maybe Restangular is the way to go?
Here is my technique:
I declare a factory called dataService, which is a wrapper around Restangular, extended with some other features.
First let me gave some code and then explain:
.factory('identityMap',
var identityMap = {};
return {
insert: function(className, object) {
if (object) {
var mappedObject;
if (identityMap[className]) {
mappedObject = identityMap[className][object.id];
if (mappedObject) {
extend(mappedObject, object);
} else {
identityMap[className][object.id] = object;
mappedObject = object;
}
} else {
identityMap[className] = {};
identityMap[className][object.id] = object;
mappedObject = object;
}
return mappedObject;
}
},
remove: function(className, object) {
if (identityMap[className] && identityMap[className][id]) delete identityMap[className][id];
},
get: function(className, id) {
return identityMap[className] && identityMap[className][id] ? identityMap[className][id] : null;
},
flush: function(){
identityMap = {};
}
};
}
.factory('modelService', ['Restangular', 'identityMap', '$rootScope', '$log', function(Restangular, identityMap, $rootScope, $log) {
var ENUM1 = {STATE:0, OTHER_STATE:1, OTHER_STATE2: 2},
ENUM2 = {OK:0, ERROR:1, UNKNOWN:2};
function extendModel(obj, modelExtension, modelName){
angular.extend(obj, modelExtension);
obj.initExtension();
obj = identityMap.insert(modelName, obj);
}
function broadcastRestEvent(resourceName, operation, data){
$rootScope.$broadcast(resourceName + $filter('capitalize')(operation), data);
}
var resource1Extension = {
_extensionFunction1: function() {
// ... do something internally ...
if (this.something){
// this.newValue ....
;
}
else {
// ....;
}
},
publicExtensionFunction: function(param1) {
// return something
},
initExtension: function() {
this._extensionFunction2();
extendModel(this.resource2, resource2Extension, 'resource2');
}
};
var resorce2Extension = {
_extensionFunction1: function() {
// do something internally
},
publicExtensionFunction = function(param1) {
// return something
},
initExtension: function(){
this._extensionFunction1;
}
};
var modelExtensions = {
'resource1': resource1Extension,
'resource2': resorce2Extension
};
var rest = Restangular.withConfig(function(RestangularConfigurer) {
RestangularConfigurer.setBaseUrl('/api');
RestangularConfigurer.setOnElemRestangularized(function(obj, isCollection, what, Restangular){
if (!isCollection) {
if (modelExtensions.hasOwnProperty(what)) {
extendModel(obj, modelExtensions[what], what);
}
else {
identityMap.insert(what, obj);
}
if (obj.metadata && obj.metadata.operation) {
broadcastRestEvent(what, obj.metadata.operation, obj);
}
}
return obj;
});
RestangularConfigurer.addResponseInterceptor(function(data, operation, what, url, response, deferred) {
var newData;
if (operation === 'getList') {
newData = data.objects;
newData.metadata = {
numResults: data.num_results,
page: data.page,
totalPages: data.total_pages,
operation: operation
};
data = newData;
}
else if (operation === 'remove') {
var splittedUrl = url.split('/');
var id = splittedUrl.pop();
var resource = splittedUrl.pop();
identityMap.remove(resource, id);
broadcastRestEvent(resource, operation, id);
}
else {
data.metadata = {operation: operation};
}
return data;
});
});
return {
rest: rest,
enums: {
ENUM1: ENUM1,
ENUM2: ENUM2
},
flush: identityMap.flush,
get: identityMap.get
}
}]);
1) Let me explain identityMap (it's the code from this blog post with some extended features):
Let's consider a REST model which looks like this (each resource represents a database table):
resource 1:
id = Integer
field1 = String
field2 = String
resource2s = [] (List of resources2 which points to this resource with their foreign key)
resource 2:
id = Integer
field1 = String
field2 = String
...
resource1_idfk = Foreign Key to resource 1
Resource API is so smart that it returns resource1 relationships with resources2 with GET /api/resource1/1 to save the overhead that you would get with GET to resource2 with some query parameters to resource1_idfk...
The problem is that if your app is doing the GET to resource1 and then somewhere later GET to resource2 and edits the resource2, the object representing the resource2 which is nested in resource1 would not know about the change (because it is not the same Javascript object reference)
The identity map solves this issue, so you hold only one reference to each resource's instance
So, for example, when you are doing an update in your controller the values automatically updates in the other object where this resource is nested
The drawback is that you have to do memory management yourself and flush the identity map content when you no longer need it. I personally use Angular Router UI, and define this in a controller which is the root of other nested states:
$scope.$on("$destroy", function() {
modelService.flush();
});
The other approach I use within the Angular Router UI is that I give the id of the resource which i want to edit/delete within that controller as the parameter of nested state and within the nested state i use:
$scope.resource1instance = modelService.get('resource1', $stateParams.id);
You can than use
resource1.put(...).then(
function(){
// you don't need to edit resource1 in list of resources1
$state.go('^');
}
function(error){
handleError(error);
});
2) When I need to use some new functionality over resources I use `Restangular's setOnElemRestangularized. I think the code above is self explanatory and very similar to the one mentioned in blog post I have mentioned above. My approach is slightly different from the one in that post, that I don't use the mixin initialization before, but after I mix it to the object, so one could reference the new functions in initializer. The other thing I don't use, for example, he creates single factory for every resource, for example Proposal for extended logic and the other factory ProposalSvc for manipulating the instances. For me that's a lot of code you don't have to write and personally I think that Javascript is not suited very well for this object oriented approach, so I return just the whole Restangular object and do operations with it.
3) Another thing I have there is the broadcast of events when something in my model changes with Restangular, this is something I needed when I used ng-table. For example, when the model changed and rows in my table needed to be updated to reference the changes, so in the controller which manages the table I use $scope.on('eventName') and then change appropriate row. These events are also great when you have a multiuser live application and you use websockets for server notifications (code not included here in modelService). For example somebody deletes something in a database, so the server sends a notification to everyone who is alive through websocket about the change, you then broadcast the same event as used in Restangular and the controller does the same edits on its data.
This blog post should help you make your choice http://sauceio.com/index.php/2014/07/angularjs-data-models-http-vs-resource-vs-restangular/
I agree that there are a lot of good practices using http headers in Restangular, but you can pick them in the source and use them directly.
What you have to wonder is, will you be able to wrap your nested resources within a $resource and make instance calls while modifying the parent object. And that's not a given.
Your question seems to be asking whether you should be using ngResource, Restangular or some other framework or drop down to the low-level and use $http directly.
$resource is still widely used because it's included in the official docs and in all the popular tutorials and articles but Restangular is fairly popular.
The website ngModules shows a listing of REST API modules for AngularJS.
If you have a simple REST API, go with $resource for now and then switch to Restangular if you're doing lots of custom coding and filtering. It is a much nicer framework and more extensible.

Waiting for User input before continuing to Marionette Application next stage

My application needs a function to be called at initialize:after stage. After user has provided that input in function, i want application to go in start stage. But currently, it just go to all the 3 stages without waiting for input. Can anyone suggest how can i do acheive my desired functionality?
var app= new Marionette.Application();
app.on('initialize:before', function(options){
//do Something
});
app.on('initialize:after', function(options){
//do Something
//wait for User to give input before continuing to start stage.
});
app.on('start', function(options){
//start only when User provide input.
//do Something with input.
});
var options = {};
app.start(options);
This is less a Backbone/Marionette question and more of a pure JavaScript problem. If you don't care about usability, just use window.prompt and save yourself a lot of work.
If you're not able to use alerts, then things get more complicated. User inputs are usually done using some sort of form. Unfortunately, these are asynchronous operations, meaning you can't just halt JavaScript execution while you wait for the user to get out their glasses and look for their SSN. jQuery's Deferred library is a good tool to use to handle situations like these, but you'll need to rework your code a little.
In our project we've created a the concept of a Lifecycle that works roughly as follows:
Module.lifecycle = {
step1: function() {
var dfd = new $.Deferred().resolve();
// get user input from a custom dialog control
var dialog = new MyDailogControl("What do you want to do?");
dialog.onSuccess = dfd.resolve;
dialog.onFail = dfd.reject;
dialog.show();
return dfd.promise();
},
step2: function() { /* load some data from the server */ },
step3: function() { /* get even more user input... */ },
};
Next we have a Lifecycle Walker object that pushes the lifecycle through each state; it looks something like this (from memory; you'll want to test this...)
var Walker = function(lifecycle) {
this.lifecycle = lifecycle;
this._states = _.keys(this.lifecycle);
}
_.extend(Walker.prototype, Backbone.Events, {
start: function() {
this._index = 0;
this.moveNext();
},
moveNext: function() {
var step = this.states[this._index];
this.trigger('before:' + step);
$.when(this.lifecycle[step]())
.then(function() {
this.trigger('after:' + step);
this._index++;
if (this._active < this._states.length) {
this.moveNext();
}
});
}
});
Tying the two ideas together, you'll do something like:
var walker = new Walker(MyModule.lifecycle);
walker.on('before:step1', function() { /* do something amazing */ });
walker.on('before:step2', function() { /* do something fantastic */ });
walker.on('after:step3', function() { /* do whatever */ });
walker.start();
So, we've basically implemented a bastardized Command Pattern using deferreds to solve problems like this. We hook into module.start, but there's no reason you couldn't do something similar on app.start instead.

Resources