Karma/Jasmine/PhantomJs: undefined is not a constructor - angularjs

I have an application that raise an odd error when I run tests. The error is the following :
TypeError: undefined is not a constructor (evaluating 'allKeys[i].match(/^[0-9]+$/)') in node_modules/jasmine-core/lib/jasmine-core/jasmine.js (line 2988)
test/spec/core/http/response-spec.js:92:63
loaded#http://localhost:8080/context.js:151:17
Most of those tests passes, but a very few break. Here is one of the test that breaks:
(function () {
'use strict';
describe('MyAccount.core.http.response', function () {
var ResponseInterceptor = {},
$httpProvider = {},
$window = {},
env = {},
MessageQueue = {};
beforeEach(module('MyAccount.core.environment'));
beforeEach(module('MyAccount.core.http', function (_$httpProvider_, $provide) {
$httpProvider = _$httpProvider_;
MessageQueue = {
dispatch: jasmine.createSpy('dispatch')
};
$window = {
location: {
href: jasmine.createSpy()
}
};
$provide.value('$window', $window);
$provide.value('MessageQueue', MessageQueue);
}));
beforeEach(inject(function (_$window_, _ResponseInterceptor_, _env_) {
$window = _$window_;
ResponseInterceptor = _ResponseInterceptor_;
env = _env_;
}));
describe('response status', function () {
// Asserting that 404 and 403 errors are intercepted.
angular.forEach([404, 403], function (error) {
describe('is ' + error, function () {
beforeEach(function () {
ResponseInterceptor.responseError({
status: error,
data: {
message: 'error ' + error
}
});
});
it('calls MessageQueue.dispatch with the error message', function () {
expect(MessageQueue.dispatch).toHaveBeenCalledWith('error ' + error, {
on: 'global.errors'
});
});
});
});
});
});
})();
I've been stuck on that for few hours now and can't seems to find a solution. Here are the dependencies I'm using and their versions:
karma: ^1.2.0
jasmine-core: ^2.5.0
karma-jasmine: ^1.0.2
karma-phantomjs-launcher: ^1.0.2
phantomjs: ^2.1.7
NOTE: This is a brand new yeoman application using the angular generator.

I had the same problem, but got it solved moments ago.
To repeat what I said in the comments: The error happens when you have two arrays that are equal, believe it or not. If they are unequal, you get the standard error with the differences shown.
jasmine-core 2.5.0. was published two days ago, as of this moment. I downgraded to 2.4.1., and it works.
It seems that 2.5.0. is the culprit.
Downgrade to 2.4.1., until the publisher gets it solved.
My setup: maven/frontend-maven-plugin/karma(*)/phantomJS
(*) could probably have said 'Jasmine' here as well.

Related

Testing Angular-Resource: Expected an Object, got a Resource

When trying to test some simple angular code using $resource, I end up with a Resource object which contains a $promise key and hence a failure of the form: Failure/Error: Expected Resource(...) to equal Object(...)
I was expecting to get back the object I passed to the respond method as part of httpBackend.expectGET('/books/5.json').respond(my_book). Am I using $resource wrong or is something wrong in my test?
Code
var bookApp = angular.module('bookApp',
[
'ngResource',
]
);
function BookController(scope, $resource) {
var ctrl = this;
var Book = $resource('/books/:selected.json', {selected:'#id'});
ctrl.fetch_book = function(id){
console.log('Selecting options ' + id);
Book.get({selected:id}, function(data){
console.log('Received: ' + JSON.stringify(data));
ctrl.current_book = data;
});
};
}
BookController.$inject = ['$scope', '$resource'];
bookApp.component('book', {
controller: BookController
});
Test
describe('component: tree', function() {
var component_controller, $componentController, httpBackend, my_book;
beforeEach(module('bookApp'));
beforeEach(inject(function($httpBackend, _$componentController_) {
httpBackend = $httpBackend;
$componentController = _$componentController_;
}));
describe('$ctrl.fetch_book(book_id)', function(){
beforeEach(function() {
component_controller = $componentController('book');
my_book = {title: 'Sanctuary', id: '5'};
});
it('fetches the book with id=book_id', function(){
httpBackend.expectGET('/books/5.json').respond(my_book);
component_controller.fetch_book(5);
httpBackend.flush();
console.log('Options: ' + JSON.stringify(component_controller.current_book));
console.log('constructor: ' + JSON.stringify(component_controller.current_book.constructor.name));
expect(component_controller.current_book).toEqual(my_book);
});
});
});
Result
$ bundle exec teaspoon -f documentation
component: tree
$ctrl.fetch_book(book_id)
fetches the book with id=book_id (FAILED - 1)
# Selecting options 5
# Received: {"title":"Sanctuary","id":"5"}
# Options: {"title":"Sanctuary","id":"5"}
# constructor: "Resource"
Failures:
1) component: tree $ctrl.fetch_book(book_id) fetches the book with id=book_id
Failure/Error: Expected Resource({ title: 'Sanctuary', id: '5',
$promise: Promise({ $$state: Object({ status: 1, value:
<circular reference: Object> }) }), $resolved: true }) to equal
Object({ title: 'Sanctuary', id: '5' }).
Finished in 0.02600 seconds
1 example, 1 failure
Try this in your tester:
expect(component_controller.current_book).toEqual(angular.toJSON(my_book));
It'll strip the object's properties and you'll have a match.
You can also try angular.equals but I haven't tested that.
Try adding the following to your spec file and see if it works. I saw it in the PhoneCat example and it worked for me.
beforeEach(function() {
jasmine.addCustomEqualityTester(angular.equals);
});
You can try doing something like this:
expect(component_controller.current_book.toJSON()).toEqual(my_book);
I had the same issue where I got an error of
Expected object to be a kind of Object, but was Resource(
This is what I had before:
expect(self.project).toEqual(mockProject);
And after I added .toJSON() it was all good:
expect(self.project.toJSON()).toEqual(mockProject);
Hope this helps!

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.

How can I use _lodash debounce with AngularJS?

It was suggested I could use the following:
$wmdInput.on('keyup', _.debounce(function () {
var rawContent = $wmdInput.val();
scope.$apply(function () {
ngModel.$setViewValue(rawContent);
});
}), 300);
However this gives a message: Uncaught TypeError: Object 500 has no method 'apply' from jQuery.
Does anyone know how I could fix this?
As an FYI it was also suggested I could use the following:
var promise;
$wmdInput.on('keyup', function () {
$timeout.cancel(promise);
promise = $timeout(function() {
var rawContent = $wmdInput.val();
ngModel.$setViewValue(rawContent);
}, 2000);
});
I would appreciate comments from the AngularJS experts here. Would the second code work as well as using _lodash? I noticed a lot of posts on github so I hope to see something implemented in the core AngularJS soon.
The (now deleted) answer from #Satpal was correct: you are passing the timeout value (300) as an argument to $wmdInput.on, and not _.debounce.
So try this:
$wmdInput.on('keyup', _.debounce(function () {
var rawContent = $wmdInput.val();
scope.$apply(function () {
ngModel.$setViewValue(rawContent);
});
}, 300));

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

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