this is my controller:
angular
.module('studentsApp')
.controller('StudentsController', StudentsController);
function StudentsController($scope, StudentsFactory) {
$scope.students = [];
$scope.specificStudent= {};
var getStudents = function() {
StudentsFactory.getStudents().then(function(response) {
if($scope.students.length > 0){
$scope.students = [];
}
$scope.students.push(response.data);
});
};
}
This is my factory:
angular.module('studentsApp')
.factory('StudentsFactory', function($http) {
var base_url = 'http://localhost:3000';
var studentsURI = '/students';
var studentURI = '/student';
var config = {
headers: {
'Content-Type': 'application/json'
}
};
return {
getStudents: function() {
return $http.get(base_url + studentsURI);
}
};
});
And here is how I'm trying to unit test the controller:
describe('Controller: Students', function() {
var StudentsController, scope, StudentsFactory;
beforeEach(function() {
module('studentsApp');
inject(function($rootScope, $controller, $httpBackend, $injector) {
scope = $rootScope.$new();
httpBackend = $injector.get('$httpBackend');
StudentsFactory = $injector.get('StudentsFactory');
StudentsController = $controller('StudentsController', {
$scope : scope,
'StudentsFactory' : StudentsFactory
});
students = [{
name: 'Pedro',
age: 10
}, {
name: 'João',
age: 11
}, {
name: 'Thiago',
age: 9
}];
spyOn(StudentsFactory, 'getStudents').and.returnValue(students);
});
});
it('Should get all students', function() {
scope.students = [];
StudentsController.getStudents();
$scope.$apply();
expect(scope.students.length).toBe(3);
});
});
The problem is when I run the test, the following message is displayed:
undefined is not a constructor (evaluating
'StudentsController.getStudents()')
I looked at the whole internet trying to find a tutorial that can help me on that, but I didn't find anything, could someone help me here?
It's link to the fact that the function getStudent() is private (declared by var). Thus your test can't access it. You have to attach it to the $scope or this to be able to test it.
I generally use this in controller:
var $this = this;
$this.getStudents = function() {
...
};
There's no StudentsController.getStudents method. It should be
this.getStudents = function () { ... };
Mocked StudentsFactory.getStudents returns a plain object, while it is expected to return a promise.
$controller shouldn't be provided with real StudentsFactory service as local dependency (it is already provided with it by default):
var mockedStudentsFactory = {
getStudents: jasmine.createSpy().and.returnValue($q.resolve(students))
};
StudentsController = $controller('StudentsController', {
$scope : scope,
StudentsFactory : mockedStudentsFactory
});
Related
Given the following test.
The $provided service is not being injected. If I debug the test in karma I can see that the service being provided is the real one, and not the mock.
The really weird thing, is that if I remove the $provide.service... I get an error Error: [$injector:unpr] Unknown provider: ficaServiceProvider <- ficaService. This clearly means that the service is getting registered, just not replaced?
describe("component: FicaStatusComponent",
function () {
var fs;
beforeEach(function () {
module("aureus",
function ($provide) {
$provide.service("ficaService", function () {
this.status = function () {
return $q(function (resolve, reject) {
resolve([{ documentType: { id: 1 } }]);
});
}
})
});
});
beforeEach(inject(function (_$componentController_, _ficaService_) {
$componentController = _$componentController_;
fs = _ficaService_;
}));
it("should expose a `fica` object", function () {
console.log('should expose');
var bindings = {};
var ctrl = $componentController("ficaStatus", null, bindings);
expect(ctrl.fica).toBeDefined();
});
it("compliant with no documents should not be compliant",
function () {
var ctrl = $componentController("ficaStatus");
expect(ctrl.fica.length).toEqual(1);
});
}
);
The second test compliant with no documents... is failing.
Chrome 56.0.2924 (Windows 10 0.0.0) component: FicaStatusComponent compliant with no documents should not be compliant FAILED
Error: Unexpected request: GET api/fica/status/
I have also tried this, expecting to have an empty object injected, but the "real" service is there nevertheless?
module("aureus", function($provide) {
$provide.value("ficaService", function() { return {}; });
$provide.service("ficaService", function() { return {}; });
});
Here is the implementation of the controller for the component:
var FicaStatusController = (function () {
function FicaStatusController($log, $loc, ficaService) {
var _this = this;
this.$log = $log;
this.$loc = $loc;
this.ficaService = ficaService;
this.fica = [];
this.ficaService.status(1234).then(function (_) { return _this.fica = _; });
}
FicaStatusController.$inject = ["$log", "$location", "IFicaStatusService"];
module("aureus").component("ficaStatus", new FicaStatusComponent());
module("aureus").service("IFicaStatusService", FicaStatusService);
The service is as follows:
var FicaStatusService = (function () {
function FicaStatusService($log, $http) {
this.$log = $log;
this.$http = $http;
}
FicaStatusService.prototype.status = function (accountNumber) {
var url = "api/fica/status/" + accountNumber;
this.$log.log("status: " + url);
return this.$http
.get(url)
.then(function (_) { return _.data; });
};
return FicaStatusService;
}());
...
You have added your service in your module like this:
module("aureus").service("IFicaStatusService", FicaStatusService);
That means that you will need to provide IFicaStatusService instead of ficaService with $provide.
I'm lost. I try to test if state.go is called after a ApiServiceMock has resolved a promise after a fake login.
For this test I get:
Expected spy go to have been called.
If I test another function which is directly triggering state.go it works. So I guess the promise isn't being resolved in the first place.
Using Angular Components and testing is quite new to me. Would be great if someone could give me a hint what I'm doing wrong or let me know if the whole approach is wrong in the first place.
login.component.ctrl.js
module.exports = function LoginComponentCtrl($state, $log, apiService) {
var vm = this;
vm.submit = function() {
apiService
.login(vm.username, vm.password)
.then(function(response) {
$state.go('storeDetail');
})
.catch(function(errData) {
$log.error(errData);
});
};
};
login.spec.js
var app = require('../../app.js');
var login = require('./login.module.js');
describe('login', function() {
var controller;
var element;
var scope;
var state;
var ApiServiceMock;
var StateMock;
var deferred;
beforeEach(angular.mock.module(app));
beforeEach(angular.mock.module(login));
describe('Component: login', function () {
beforeEach(inject(function($rootScope, $compile, $componentController, $q){
deferred = $q.defer();
ApiServiceMock = {
login: function () {
return deferred.promise;
}
};
StateMock = {
go: function () {
return true;
}
};
scope = $rootScope.$new();
controller = $componentController('login', {
$scope: scope,
apiService: ApiServiceMock,
$state: StateMock
});
element = angular.element('<login></login>');
element = $compile(element)(scope);
scope.$apply();
}));
it('on successful login', function() {
controller.username = 'Michael Jackson';
controller.password = 'dangerous123';
spyOn(StateMock, 'go').and.callThrough();
controller.submit();
deferred.resolve();
scope.$digest();
expect(StateMock.go).toHaveBeenCalled();
}););
});
});
Let me know if I can add further information to make this clearer.
Just directly return resolved promise from your mock service.
Try this
beforeEach(inject(function($rootScope, $compile, $componentController, $q){
ApiServiceMock = {
login: function () {
return $q.resolve({}); // if angular version is 1.4+
//return $q.when({}); // if angular less than 1.4
}
};
StateMock = {
go: function () {
return true;
}
};
scope = $rootScope.$new();
controller = $componentController('login', {
$scope: scope,
apiService: ApiServiceMock,
$state: StateMock
});
element = angular.element('<login></login>');
element = $compile(element)(scope);
scope.$apply();
}));
it('on successful login', function() {
controller.username = 'Michael Jackson';
controller.password = 'dangerous123';
spyOn(StateMock, 'go').and.callThrough();
controller.submit();
scope.$digest();
expect(StateMock.go).toHaveBeenCalled();
});
);
I'm new to angularjs. In my webapp I'm trying to work with user contacts as follows.
SERVICE
app.service('Contacts', function ($http,$timeout,$q) {
return {
getData: function() {
var defer = $q.defer();
$http.get('../ListContacts')
.success(function(data) {
defer.resolve(data);
});
return defer.promise;
}
}
});
ContactsController, OtherControllers
$scope.contactsBook = {};
...
Contacts.getData().then(function(data) {
$scope.contactsBook = data;
});
I found the above method somewhere in SO itself. I used it because I don't want to use separate module for Contacts.
I can get data at page load. I can update my contacts at server through ajax posts (from ContactsController). Now I only need a way to update(/refresh) the list automatically in all controllers. How can I achieve that.
I found these three links related but being a newbie I'm unable to figure my way out.
While it is understandable that you may not want to update your current architecture, it may be necessary to adjust your calls slightly if you want to be able to easily share data between controllers via a service.
One flexible approach is to store the data in your service and register watchers in each controller. This allows you to call the service update from one controller (the Contacts controller) and have the change be reflected in all consuming controllers. Note the service is mocked.
You can find the working plunker example here.
Contacts Service:
var app = angular.module('app', []);
app.service('contactsService', function ($http) {
var contacts = [];
return {
loadData: function() {
var mockGet = $q.defer();
var data = [
{ id: 1, name: 'Jack' },
{ id: 2, name: 'Jill' }
];
contacts = data;
mockGet.resolve(contacts);
return mockGet.promise;
},
retrieveNewData: function() {
var mockGet = $q.defer();
var data = [
{ id: 1, name: 'Jack' },
{ id: 2, name: 'Jill' },
{ id: 3, name: 'Bob' },
{ id: 4, name: 'Susan' }
];
contacts = data;
mockGet.resolve(contacts);
return mockGet.promise;
},
getContacts: function () {
return contacts;
}
}
});
Contacts Controller:
app.controller('ContactsCtrl', ['$scope', 'contactsService',
function ($scope, contactsService) {
var vm = this;
vm.contacts = [];
vm.loadData = loadData;
vm.retrieveNewData = retrieveNewData;
$scope.$watch(angular.bind(contactsService, function () {
return contactsService.getContacts();
}), function (newVal) {
vm.contacts = newVal;
});
function loadData() {
contactsService.loadData();
}
function retrieveNewData() {
contactsService.retrieveNewData();
}
}
]);
Other Controller:
app.controller('OtherCtrl', ['$scope', 'contactsService',
function($scope, contactsService) {
var vm = this;
vm.contacts = [];
$scope.$watch(angular.bind(contactsService, function () {
return contactsService.getContacts();
}), function (newVal) {
vm.contacts = newVal;
});
}
]);
You can do
Contacts.getData().then(function(data) {
$scope.contactsBook = data;
$scope.$emit('contacts:updated', data);
});
And then, where you need to notify the controller about the update:
$rootScope.$on('contacts:updated', function(e, contacts) {
$scope.contacts = contacts;
});
Another approach
The service is holding the current contacts list
app.service('Contacts', function ($http,$timeout,$q) {
this.currentList = [];
this.getData = function() {
var defer = $q.defer();
$http.get('../ListContacts')
.success(function(data) {
defer.resolve(data);
});
return defer.promise;
}
});
In your controller:
Contacts.getData().then(function(data) {
$scope.contactsBook = data;
Contacts.currentList = data;
});
In other controller:
controller('AnotherController', function($scope, Contacts) {
$scope.contacts = Contacts.currentList;
});
If you are going to return an object literal you will need to turn your .service() into a .factory() module . In this case I'll be using a service module .
Example
Your service .
app.service('Contacts', function ($http,$timeout,$q) {
var Contacts = this;
contacts.getData = function() {
var defer = $q.defer();
$http.get('../ListContacts')
.success(function(data) {
defer.resolve(data);
});
return defer.promise;
}
}
return Contacts;
});
You will then need to inject this server into your ContactsController .
app.controller('ContactsController', function(Contacts){
$scope.data = null;
$scope.init = function(){
Contacts.getData().then(function(response){
$scope.data = response;
})
}
})
now data can be used in dom
Example
<li ng-repeat="x in data">{{x.name}}</li>
i try to test an angular controller but he returns a promise and i cant really resolve it...what's wrong with my code?
the Angular Controller:
kpi.AboutController = function ($scope, Version) {
function loadVersion() {
//$scope.version = Version.getVersion();
$scope.version = '1.1.0';
}
loadVersion();
};
and the jasmine test:
describe('dashboard: AboutController', function() {
beforeEach(module('dashboard'));
var $controller;
beforeEach(inject(function(_$controller_){
// The injector unwraps the underscores (_) from around the parameter names when matching
$controller = _$controller_;
}));
it('testing the version number', function() {
var $scope = {};
var controller = $controller('AboutController', { $scope: $scope });
var version = 0;
console.log($scope.version);
expect($scope.version).toContain("1.");
});
});
this is working, but when i change the line $scope.version = '1.1.0'; to $scope.version = Version.getVersion(); i receive a promise and i can not rly check it....i tried to resolve it with "then()" function via $scope.version.then(...)but that did not work...what to do?
edit:
the following error occures:
Expected e({ $promise: Object({ $$state: Object({ status: 0 }) }), $resolved: false }) to contain '1.'.
at Object.<anonymous>
and the Service:
kpi.VersionFactory = function ($resource, appConfig) {
var Version = $resource(thePath, {}, {
"getVersion": {
method: "GET",
url: thePath,
isArray: false
}
});
return Version;
};
you need to pass a callback into your test case .
kpi.AboutKPIController = function ($scope, Version) {
$scope.loadVersion= function () {
Version.getVersion().then(function(res){
$scope.version = res.data;
})
}
$scope.loadVersion();
};
describe('dashboard: AboutKPIController', function() {
beforeEach(module('dashboard'));
var $controller;
beforeEach(inject(function(_$controller_){
// The injector unwraps the underscores (_) from around the parameter names when matching
$controller = _$controller_;
}));
it('testing the version number', function(done) {
var $scope = {};
var controller = $controller('AboutKPIController', { $scope: $scope });
var version = 0;
console.log($scope.version);
$scope.loadVersion().then(function(res)){
//can test here
expect($scope.version).toContain("1.");
done();
});
});
});
I expect Version.getVersion(); returns a promise. Your controller implemnentation should look like
function loadVersion() {
Version.getVersion().then(function(version) {
$scope.version = version;
});
}
In your test code use $scope.$apply to resolve promises before you perform except.
beforeEach(inject(function(_$controller_, _$rootScope_){
$controller = _$controller_;
$rootScope = _$rootScope_;
}));
it('testing the version number', function() {
var $scope = $rootScope.$new();
var controller = $controller('AboutKPIController', { $scope: $scope });
controller.loadVersion();
$scope.$apply();
expect($scope.version).toContain("1.");
});
I am having a bit of trouble getting set up to test an Angular controller that contains promises.
The controller code it this:
angular.module('jhApp')
.controller('adminPagesCtrl', function(resourceCache) {
var adminPages = this;
adminPages.items = resourceCache.query('page');
adminPages.delete = function(page) {
resourceCache.delete('page', {id:page._id})
.then(function(responceData) {
if(responceData.deleted === true) {
adminPages.items = resourceCache.query('page');
}
});
};
});
my test looks like this:
describe('adminPagesCtrl', function() {
var defferred,
$rootScope,
controller,
resourceCache,
scope,
page,
defferred,
promise;
beforeEach(function() {
module('jhApp');
});
beforeEach(inject(function ($rootScope, $controller, $q) {
scope = $rootScope.$new();
controller = $controller('adminPagesCtrl as adminPages', {$scope: scope});
deffered = $q.defer();
promise = deffered.promise;
resourceCache = {
delete: promise
};
page = {_id: 1};
spyOn(resourceCache, 'delete');
}));
it('deletes a page', function() {
expect(controller).toBeDefined();
scope.adminPages.delete(page);
console.log(resourceCache.delete) //outputs: function{}
console.log($rootScope) //outputs: undefined
resourceCache.delete.resolve({deleted: true});
$rootScope.$apply();
expect(resourceCache.delete).toHaveBeenCalled();
});
});
I am trying to mock the resourceCache promise so it returns some fake data and so I can just test that something got returned and the adminPages.delete calls the the resourceCache.delete.
I think I am doing something fundamentally wrong though as the current error is:
undefined is not a fuction
This i am sure is because if I try to log out resourceCache.delete it just shows and empty function. The first expect
resourceCache.delete.resolve();
passes ok.
You need to setup resourceCache.delete as a function that returns a promise rather than just set to a promise. You should also be mocking resourceCache.query. To resolve the promise you need to use deffered.resolve(response); after controller.delete is called. Then $rootScope.$digest();.
describe('adminPagesCtrl', function() {
var createController, $rootscope, deferred, resourceCache;
beforeEach(module('jhApp'));
beforeEach(inject(function($controller, _$rootScope_, $q) {
$rootScope = _$rootScope_;
deferred = $q.defer();
resourceCache = {
delete: function () {
},
query: function (page) {
}
};
spyOn(resourceCache, 'delete').and.returnValue(deferred.promise);
createController = function() {
return $controller('adminPagesCtrl', { resourceCache: resourceCache } );
};
}));
it('deletes a page', function() {
//Arrange
var controller = createController();
var page = {
_id: 1
};
var response = {
deleted: true
};
var items = [{
test: 'test'
}];
var expectedDeleteParam = {
id: page._id
};
spyOn(resourceCache, 'query').and.returnValue(items);
//Act
controller.delete(page);
deferred.resolve(response);
$rootScope.$digest();
//Assert
expect(resourceCache.delete).toHaveBeenCalledWith('page', expectedDeleteParam);
expect(resourceCache.query).toHaveBeenCalledWith('page');
expect(controller.items).toEqual(items);
});
});
Plunkr
I finally got this working so in case anyone else has a similar issue here is the amended version. Calling $rootScope.digest() was causing an error with Karma.
Error: Unexpected request: GET views/projects.html
No more request expected
I swapped that for scope = $rootScope.$new(); and now tests are passing.
describe('adminPagesCtrl', function() {
var createController, $rootScope, deferred, resourceCache, scope;
beforeEach(module('jhApp'));
beforeEach(inject(function($controller, $rootScope, $q) {
scope = $rootScope.$new();
deferred = $q.defer();
resourceCache = {
delete: function () {
},
query: function (page) {
}
};
spyOn(resourceCache, 'delete').and.returnValue(deferred.promise);
createController = function() {
return $controller('adminPagesCtrl', { resourceCache: resourceCache } );
};
}));
it('deletes a page', function() {
//Arrange
var controller = createController();
var page = {
_id: 1
};
var response = {
deleted: true
};
var items = [{
test: 'test'
}];
var expectedDeleteParam = {
id: page._id
};
spyOn(resourceCache, 'query').and.returnValue(items);
//Act
controller.delete(page);
deferred.resolve(response);
scope.$digest();
//Assert
expect(resourceCache.delete).toHaveBeenCalledWith('page', expectedDeleteParam);
expect(resourceCache.query).toHaveBeenCalledWith('page');
expect(controller.items).toEqual(items);
});
});