Using a service in a unit test, angular - angularjs

I have this controller:
angular.module('clientApp')
.controller('MainCtrl', function ($scope, projects) {
$scope.projects = projects;
});
projects is a resolve from a database. It works in the view.
This is my service:
angular.module('clientApp.services', ['ngResource'])
.factory('Projects', function($resource){
return $resource('/api/project/:prj_id', {'prj_id':'#prj_id'});
})
.factory('MultiProjectsLoader',['Projects', '$q', '$stateParams',
function(Projects, $q) {
return function() {
var delay = $q.defer();
Projects.query(function(projects) {
delay.resolve(projects);
}, function() {
delay.reject('Unable to fetch sizes');
});
return delay.promise;
};
}
]);
And this is my app.js
$stateProvider
.state('home', {
url: '/',
templateUrl: 'views/home.html',
resolve:{
projects: ['MultiProjectsLoader', function(MultiProjectsLoader){
return new MultiProjectsLoader();
}]
},
controller: 'MainCtrl'
});
Trying to write a test for this:
'use strict';
describe('Controller: MainCtrl', function () {
// load the controller's module
beforeEach(module('clientApp'));
beforeEach(function () {
angular.module('clientApp.services');
});
var MainCtrl,
scope;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
MainCtrl = $controller('MainCtrl', {
$scope: scope
});
}));
it('should attach a list of projects to the scope', function () {
expect(scope.projects.length).toBeGreaterThan(1);
});
});
I get:
Error: [$injector:unpr] Unknown provider: projectsProvider <- projects
I guess I need to include the service somehow beforeEach(..). But I can't get it working. Any ideas?

You can inject the service a couple ways but my recommended way is to mock the service.
describe('Controller: MainCtrl', function () {
var
projectServiceMock = {
getData: function() {}
},
DATA_FROM_SERVER = {}
;
// load the controller's module
beforeEach(module('clientApp'));
beforeEach(function () {
angular.module('clientApp.services');
//angular.module('project'); // But dont use this method as you will be testing the service in the controller:
});
var MainCtrl,
scope;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
spyOn(projectServiceMock, 'getData').andReturn(DATA_FROM_SERVER);
MainCtrl = $controller('MainCtrl', {
$scope: scope,
project: projectServiceMock
});
}));
it('should attach a list of projects to the scope', function () {
expect(projectServiceMock.getData).toHaveBeenCalledWith(DATA_FROM_SERVER);
expect(scope.projects.length).toBeGreaterThan(1);
});
});
Your service should expose a method for returning the data it has gotten from the server not just straight data through project.
For example:
project.getData();

Related

Unknown provider: $scopeProvider <- $scope

I am trying to make a small test work that validates wether the controller is defined.
The error I am receiving is:
myApp.orders module Order controller should .... FAILED
Error: [$injector:unpr] Unknown provider: $scopeProvider <- $scope <- OrdersCtrl
Reading similar errors it has something to do with the dependencies, but I don't know what's wrong.
Controller:
'use strict';
angular.module('myApp.orders', ['ngRoute'])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/orders', {
templateUrl: 'orders/orders.template.html',
controller: 'OrdersCtrl'
});
}])
.controller('OrdersCtrl', function($scope, $location) {
$scope.changeView = function(view){
$location.path(view); // path not hash
}
});
Test:
'use strict';
describe('myApp.orders module', function() {
beforeEach(module('myApp.orders'));
describe('Order controller', function(){
it('should ....', inject(function($controller) {
//spec body
var OrdersCtrl = $controller('OrdersCtrl');
expect(OrdersCtrl).toBeDefined();
}));
});
});
This is because you are not passing the $scope variabl einside the controller when you are creating it in test. And the controller tries to define $scope.changeView, but it finds $scope as undefined.
You need to pass a $scope variable to the controller in your test.
var $rootScope, $scope, $controller;
beforeEach(function() {
module('myApp.orders');
inject(function (_$rootScope_, _$controller_) {
$rootScope = _$rootScope_;
$scope = _$rootScope_.$new();
$controller = _$controller_;
});
});
and in your test,
var OrdersCtrl = $controller('OrdersCtrl', { $scope: $scope });
Restructure your unit test slightly. We have a pattern where the controller is defined in the beforeEach() so it's ready for the test. You also need to import the controller you're testing:
import ControllerToTest from 'path/to/your/real/controller';
describe('myApp.orders module',() => {
let vm;
beforeEach(() => {
inject(($controller, $rootScope) => {
vm = $controller(ControllerToTest,
{
$scope: $rootScope.$new()
};
});
});
describe('Order Controller', () => {
it('should do something', () => {
expect(vm).toBeDefined();
});
});
});
Chaneg your controller like this
.controller('OrdersCtrl',['$scope', '$location', function($scope, $location) {
$scope.changeView = function(view){
$location.path(view); // path not hash
}
}]);

Mock Service Within Controller Returning Undefined

I am trying to test something pretty simple: a controller that calls a service that performs a http request.
Controller:
define(['module'], function (module) {
'use strict';
var MyController = function ($scope, MyService) {
$scope.testScope = 'karma is working!';
MyService.getData().then(function (data) {
$scope.result = data.hour
});
};
module.exports = ['$scope', 'MyService', MyController ];
});
Test:
define(['require', 'angular-mocks'], function (require) {
'use strict';
var angular = require('angular');
describe("<- MyController Spec ->", function () {
var controller, scope, myService, serviceResponse;
serviceResponse= {
id: 12345,
hour: '12'
};
beforeEach(angular.mock.module('myApp'));
beforeEach(inject(function (_$controller_, _$rootScope_, _MyService_, $q) {
scope = _$rootScope_.$new();
var deferred = $q.defer();
deferred.resolve(serviceResponse);
myService = _MyService_;
spyOn(myService, 'getData').and.returnValue(deferred.promise);
controller = _$controller_('MyController', {$scope: scope});
scope.$apply();
}));
it('should verify that the controller exists ', function() {
expect(controller).toBeDefined();
});
it('should have testScope scope equaling *karma is working*', function() {
expect(scope.testScope ).toEqual('karma is working!');
});
});
});
With the above, i get the error:
TypeError: 'undefined' is not an object (evaluating 'spyOn(myService, 'getData').and.returnValue')
Solved the question - was using Jasmine 1x.. Upgraded to 2.0 and works as expected

How to inject a service to jasmine

I have the following test case MeetingCtrlSpec.js
describe('ViewMeetingCtrl', function () {
var $rootScope, scope, $controller ;
beforeEach(angular.mock.module('MyApp'));
beforeEach(inject(function ($rootScope, $controller ) {
scope = $rootScope.$new();
$controller('ViewMeetingCtrl', {
$scope: scope,
});
}));
it('should change greeting value if name value is changed', function () {
//some assertion
});
});
ViewMeetingCtrl.js
(function () {
'use strict';
angular.module('MyApp').controller('ViewMeetingCtrl', ViewMeetingCtrl);
ViewMeetingCtrl.$inject = ['$scope', '$state', '$http', '$translate', 'notificationService', 'meetingService', '$modal', 'meeting', 'attachmentService'];
function ViewMeetingCtrl($scope, $state, $http, $translate, notificationService, meetingService, $modal, meeting, attachmentService) {
$scope.meeting = meeting;
//more code goes here
}
})();
this meeting comes from the app.routes.js file
.state('company.meeting', {
abstract: true,
url: '/meetings/:meetingId',
template: '<ui-view/>',
resolve: {
meeting: function(meetingService, $stateParams){
return meetingService
.getMeeting($stateParams.meetingId)
.then(function(response){
return response.data;
});
}
},
})
My problem is regarding the injection of meeting in this ctrl . I am not sure how to do inject that in my test case. I did like the following .
describe('ViewMeetingCtrl', function () {
var $rootScope, scope, $controller , meeting ;
beforeEach(angular.mock.module('MyApp'));
beforeEach(inject(function ($rootScope, $controller , meeting ) {
scope = $rootScope.$new();
$controller('ViewMeetingCtrl', {
$scope: scope,
meeting : meeting
});
}));
it('should change greeting value if name value is changed', function () {
//some assertion
});
});
... and i got this error Error: [$injector:unpr] Unknown provider: meetingProvider <- meeting
How do i inject meeting dependency to my test case . ?
Meeting is not a service, but an object that is injected when route is resolve. In you test case you should explicitly create the meeting dummy object.
beforeEach(inject(function ($rootScope, $controller,$q ) {
scope = $rootScope.$new();
$controller('ViewMeetingCtrl', {
$scope: scope,
meeting : {} //your custom object
});
}));
Remember you are testing the controller in your test not the route resolution injection.

Testing Bootstrap modal in Angular controller

I have been trying to find a way of testing this controller part for a few days but keep getting stuck. Now I get a ReferenceError: Can't find variable: $modal but I have it injected so im not sure why its not working. I also know that this test I am writing doesn't really test anything important so if you have any suggestions about moving forward please let me know. And thank you to anyone who has helped me on code throughout this controller
Code:
$scope.confirmDelete = function (account) {
var modalInstance = $modal.open({
templateUrl: '/app/accounts/views/_delete.html',
controller: function (global, $scope, $modalInstance, account) {
$scope.account = account;
$scope.delete = function (account) {
global.setFormSubmitInProgress(true);
accountService.deleteAccount(global.activeOrganizationId, account.entityId).then(function () {
global.setFormSubmitInProgress(false);
$modalInstance.close();
},
function (errorData) {
global.setFormSubmitInProgress(false);
});
};
$scope.cancel = function () {
global.setFormSubmitInProgress(false);
$modalInstance.dismiss('cancel');
};
},
resolve: {
account: function () {
return account;
}
}
});
Test:
describe("confirmDelete() function", function () {
var controller, scope;
// sets scope of controller before each test
beforeEach(inject(function ($rootScope, _$modal_) {
scope = $rootScope.$new();
controller = $controller('AccountsController',
{
$scope: scope,
$stateParams: mockStateParams,
$state: mockState,
// below: in order to call the $modal have it be defined and send on the mock modal?
$modal: _$modal_,
//modalInstance: mockModalInstance,
global: mockGlobal,
accountService: mockAccountSrv
});
}));
beforeEach(inject(function ($modal, $q) {
spyOn($modal, 'open').and.returnValue({
result: $q.defer().promise
});
}));
it("make sure modal promise resolves", function () {
scope.confirmDelete(mockAccountSrv.account);
expect($modal.open).toHaveBeenCalled();
});
});
You need to set modal to a variable in order to be able to use it.
i.e
describe("confirmDelete() function", function () {
var controller, scope, $modal; //Initialize it here
//....
beforeEach(inject(function ($rootScope, _$modal_, $controller) {
$modal = _$modal_; //Set it here
And you need to inject $controller as well in order to be able to use it.
Plnkr

Unit test of controller angularjs

I have this simple controller, UserService is a service which return JSON
"use strict";
angular.module("controllers").controller('profileCtrl', ["$scope", "UserService",
function ($scope, UserService) {
$scope.current_user = UserService.details(0);
}
]);
I can not make the test. However this is my try
'use strict';
describe('profileCtrl', function () {
var scope, ctrl;
beforeEach(angular.mock.module('controllers'), function($provide){
$provide.value("UserService", {
details: function(num) { return "sdfsdf"; }
});
});
it('should have a LoginCtrl controller', function() {
expect(controllers.profileCtrl).toBeDefined();
});
beforeEach(angular.mock.inject(function($rootScope, $controller){
scope = $rootScope.$new();
$controller('profileCtrl', {$scope: scope});
}));
it('should fetch list of users', function(){
expect(controllers.scope.current_user.length).toBe(6);
expect(controllers.scope.current_user).toBe('sdfsdf');
});
});
The usage of $controller is correct, that's the way to instantiate a controller for a unit test. You can mock the UserService instance it gets directly in the $controller invocation.
You should be using its return value - this is the instance of your controller you're going to test.
You're trying to read stuff from controllers but its not defined anywhere in the test, I guess you're referring to the module.
This is how I would go about it + fiddle
//--- CODE --------------------------
angular.module('controllers', []).controller('profileCtrl', ["$scope", "UserService",
function ($scope, UserService) {
$scope.current_user = UserService.details(0);
}]);
// --- SPECS -------------------------
describe('profileCtrl', function () {
var scope, ctrl, userServiceMock;
beforeEach(function () {
userServiceMock = jasmine.createSpyObj('UserService', ['details']);
userServiceMock.details.andReturn('sdfsdf');
angular.mock.module('controllers');
angular.mock.inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('profileCtrl', {
$scope: scope,
UserService: userServiceMock
});
});
});
it('should have a LoginCtrl controller', function () {
expect(ctrl).toBeDefined();
});
it('should fetch list of users', function () {
expect(scope.current_user).toBe('sdfsdf');
});
});
You're welcome to change the fiddle online to see how it affects testing results.

Resources