I'm writing some test for for an angularjs factory and some of the expectations are not working and I really don't know why.
This is my factory (part of it). 'use strict';
angular.module('myAppMod')
.factory('Person', function(BaseModel) {
return BaseModel.extend({
get fullname() {
var name = [];
if (this.first_name) {
name.push(this.first_name);
}
if (this.person_extra && this.person_extra.middle_name) {
name.push(this.person_extra.middle_name);
}
if (this.last_name) {
name.push(this.last_name);
}
return name.join(' ');
}
});
});
and Jasmine tests:
var p;
beforeEach(function() {
p = new Person({
first_name: 'first_name',
person_extra: {
middle_name: 'middle_name',
media_item_id: null
},
last_name: 'last_name',
security_level: 'security_level'
}, true);
});
it("has a fullname", function() {
expect(p.fullname).toEqual('first_name middle_name last_name');
});
p.fullname
is returning ""
(empty string) and in the factory, console.log(this.first_name)
, is undefined
.
Any help is really appreciated. Thank you in advance
I'm writing some test for for an angularjs factory and some of the expectations are not working and I really don't know why.
This is my factory (part of it). 'use strict';
angular.module('myAppMod')
.factory('Person', function(BaseModel) {
return BaseModel.extend({
get fullname() {
var name = [];
if (this.first_name) {
name.push(this.first_name);
}
if (this.person_extra && this.person_extra.middle_name) {
name.push(this.person_extra.middle_name);
}
if (this.last_name) {
name.push(this.last_name);
}
return name.join(' ');
}
});
});
and Jasmine tests:
var p;
beforeEach(function() {
p = new Person({
first_name: 'first_name',
person_extra: {
middle_name: 'middle_name',
media_item_id: null
},
last_name: 'last_name',
security_level: 'security_level'
}, true);
});
it("has a fullname", function() {
expect(p.fullname).toEqual('first_name middle_name last_name');
});
p.fullname
is returning ""
(empty string) and in the factory, console.log(this.first_name)
, is undefined
.
Any help is really appreciated. Thank you in advance
Share Improve this question edited Nov 19, 2015 at 14:22 Ricbermo asked Nov 18, 2015 at 22:28 RicbermoRicbermo 8051 gold badge6 silver badges28 bronze badges 7- 1 Can you show the plete code of the factory? Thanks. – alecxe Commented Nov 18, 2015 at 22:32
- 1 Which dialect of Javascript are you writing this in? And which browser are you running the test in? Do you know for certain that the get syntax is supported by the browser running the test? For example, maybe PhantomJS doesn't support getters ... just thinking out loud. – Sunil D. Commented Nov 19, 2015 at 3:24
- @SunilD. is plain javascript. I'm using Chrome latest on a mac. – Ricbermo Commented Nov 19, 2015 at 14:19
- @alecxe just updated the factory – Ricbermo Commented Nov 19, 2015 at 14:24
-
What is
BaseModel
? Plain JS objects don't have anextend()
method. Is it a Backbone model? – Tyler Eich Commented Dec 11, 2015 at 2:40
2 Answers
Reset to default 6 +50EDIT: After further investigation, I have changed my answer.
It is not working because you are using the getter shorthand (get fnName() { }
) through the extend
method. The getter's this
is the anonymous object itself and does not inherit the methods and properties of the Backbone model, whereas the this
in function properties do. I have made a codepen that illustrate your problem.
That is, if this is your Model
var Model = BaseModel.extend({
get isBackboneModelThroughGetter() {
return !!this.get;
},
isBackboneModel: function() {
return !!this.get;
},
});
Then an instance of Model will make this test pass:
it('should make you wonder', function() {
var model = new Model();
expect(model.isBackboneModel()).toBe(true);
expect(model.isBackboneModelThroughGetter).not.toBe(true);
});
Thus, to make your Person factory work, you will need:
- To replace every property access by the proper Backbone call:
this.get('propertyName')
instead ofthis.propertyName
- Replace all getters by function properties:
full_name : function() { /*...*/ }
instead ofget full_name() { /* ... */ }
- Replace calls to
model.full_name
bymodel.full_name()
;
I assume that you're using the built-in angular.extend
. angular.extend
does not copy getters and setters. There's been an open issue on GitHub on this specific subject since the 12th of August 2014.
As for why it still isn't implemented:
Angular exposes some of the helper functions that it uses internally. This is the case for
extend
,copy
and many others. There are other libraries that specialize in these functions, keep their focus is there and can do a better job.It is not in the best interest of most users to make these helper functions big nor slow, as these are used internally and any change in that direction can have a direct impact in download size and performance. At the same time, apps that need the most accurate version, should be better served with other libraries.
There are many ways to solve this issue. decaf.js
provides an example implementation that should work for most cases. GitHub is probably a better environment to dive into their code, but it es down to this:
function extend (me) {
var args = Array.prototype.slice.call(arguments, 1);
decaf.each(args, function (o) {
for (var key in o) {
if (o.hasOwnProperty(key)) {
var desc = Object.getOwnPropertyDescriptor(o, key);
var g = desc.get;
var s = desc.set;
if (g || s) {
Object.defineProperty(me, key, { get: g, set: s, enumerable: true });
} else {
me[key] = o[key];
}
}
}
});
return me;
}