So I am trying to learn how to unit test with Jasmine with Angular. I have got a number of unit tests working but this one has stumped me. I took out the alerts array in my test you can make it any array.. But how to mock this and getting this to work has really stumped me. I would think that the object would exist.
(function () {
var app = angular.module("TabDirectives", ["Communication"]);
app.directive("sentAlerts", ["DirectiveProvider", "DataProvider", function (DirectiveProvider, DataProvider) {
var dir = DirectiveProvider("SentAlerts");
dir.controller = function () {
var ctrl = this;
DataProvider("AlertsByDate").then(function (Result) {
ctrl.AlertList = Result.data;
});
};
dir.controllerAs = "Alerts"
return dir;
}]);
})()
I have a test that looks like
describe("Tab Directive Unit Tests", function () {
var controller;
describe("Tab Directive Default Values", function () {
beforeEach(inject(function ($rootScope, $compile) {
element = angular.element("<sent-alerts></sent-alerts>");
$compile(element)($rootScope.$new());
$rootScope.$digest();
controller = element.controller;
}));
it("Ctrl should be this", function () {
expect(controller.ctrl).toBe(controller.this);
});
it("AlertList should have Alerts", function () {
expect(controller.ctrl.AlertList).toBe(alerts);
});
});
});
The error I'm getting looks like
TypeError: Cannot read property 'AlertList' of undefined
You have to initialize and inject your controller as well. Something like this:
var $controller;
var $rootScope;
var scope;
var controller;
beforeEach(inject(function (_$controller_, _$rootScope_) {
$controller = _$controller_;
$rootScope = _$rootScope_;
scope = $rootScope.$new();
controller = $controller('ScopeController', { '$scope': scope });
}));
Related
I'm very new to the AngularJs unit testing with Jasmine.So could you tell me how can I test below mentioned controller and countyService.getAllCountiesAsync() method using Jasmine.Thanks in advance.
Note : The controller below is having more than 50 injected services (I have shown few below).So I don't know which method is good for mock those also ?
Controller :
(function () {
appModule.controller('myController', [
'$scope', '$modalInstance', 'abp.services.app.property', 'abp.services.app.county', 'abp.services.app.propertyClass', 'abp.services.app.schoolDistrict'
function ($scope, $modalInstance, propertyService, countyService, propertyClassService, schoolDistrictService) {
vm.getAllCounties = function () {
countyService.getAllCountiesAsync().success(function (result) {
vm.counties = result.items;
});
};
vm.getAllCounties();
} ]);
})();
WebApi method :
public async Task<ListResultOutput<CountyListDto>> GetAllCountiesAsync()
{
var counties = await _countyRepository
.GetAllListAsync();
return new ListResultOutput<CountyListDto>(counties.OrderBy(o => o.Name).MapTo<List<CountyListDto>>());
}
You should write test cases for service and controller.
For services 'Daan van Hulst' has already given answer and for controller see below code:
describe('service tests', function () {
var $compile,$controller,myController, $rootScope, propertyService, countyService, propertyClassService, schoolDistrictService;
//All module dependencies
beforeEach(module('your-app-name'));
//inject required services and _$controller_ to create controller
beforeEach(inject(function(_$compile_,_$controller_, _$rootScope_, _propertyService_, _countyService_, _propertyClassService_, _schoolDistrictService_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$controller = _$controller_; // This is IMP
countyService = _countyService_;
// remianig services
// Now create controller
myController = $controller('myController', {
$scope : scope,
propertyService : propertyService // all other services
});}
it('should test something', function() {
spyOn(countyService, 'getAllCountiesAsync').and.callFake(function () {
var d = q.defer();
d.resolve({ items: [{data:'somedata'}] });
return d.promise;
});
myController.getAllCounties();
expect(myController.counties).not.toBe(null);
});
Update
I might have made mistakes, but this is the idea:
describe('service tests', function () {
var $compile, $rootScope, scope, vm, propertyService, countyService, propertyClassService, schoolDistrictService;
beforeEach(module('your-app-name'));
beforeEach(inject(function(_$compile_, _$rootScope_, $controller, _propertyService_, _countyService_, _propertyClassService_, _schoolDistrictService_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
scope = $rootScope.$new();
propertyService = _propertyService_;
countyService = _countyService_;
propertyClassService = _propertyClassService_;
schoolDistrictService = _schoolDistrictService_;
vm = $controller('myController', {'$scope': scope})
spyOn(countyService, "getAllCountiesAsync").and.callFake(function() {
var deferred = $q.defer();
deferred.resolve({data: [{id:0}]});
return deferred.promise;
});
}));
it('can do remote call', inject(function() {
//Arrange
result = [{id:0}];
// Act
vm.getAllCounties();
// Assert
expect(vm.counties).toBe(result); //assert to whatever is resolved in the spyOn function
});
});
}
I assume that you create Angular services for all your services and that you app is working. Then, you can inject them in your tests:
describe('service tests', function () {
var $compile, $rootScope, propertyService, countyService, propertyClassService, schoolDistrictService;
beforeEach(module('your-app-name'));
beforeEach(inject(function(_$compile_, _$rootScope_, _propertyService_, _countyService_, _propertyClassService_, _schoolDistrictService_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
propertyService = _propertyService_;
countyService = _countyService_;
propertyClassService = _propertyClassService_;
schoolDistrictService = _schoolDistrictService_;
}));
it('should test something', function() {
expect(propertyService).toBeDefined();
expect(countyService).toBeDefined();
expect(propertyClassService).toBeDefined();
expect(schoolDistrictService).toBeDefined();
});
});
Update
I accidentally posted my solution in the answer above, so corrected it now. You can create your controller with $controller and pass in a scope object. You can also pass in any other dependencies. Then create a spy on the service, and once it gets called, call a different function which resolves a promise with mock data:
describe('service tests', function () {
var $compile, $rootScope, scope, vm, propertyService, countyService, propertyClassService, schoolDistrictService;
beforeEach(module('your-app-name'));
beforeEach(inject(function(_$compile_, _$rootScope_, $controller, _propertyService_, _countyService_, _propertyClassService_, _schoolDistrictService_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
scope = $rootScope.$new();
propertyService = _propertyService_;
countyService = _countyService_;
propertyClassService = _propertyClassService_;
schoolDistrictService = _schoolDistrictService_;
// Create the controller, and pass in the scope with possible variables that you want to mock.
vm = $controller('myController', {'$scope': scope})
//Create a spy on your getAllCountiesAsync function and make it return a mock promise with mock data.
spyOn(countyService, "getAllCountiesAsync").and.callFake(function() {
var deferred = $q.defer();
deferred.resolve({data: [{id:0}]});
return deferred.promise;
});
}));
it('can do remote call', inject(function() {
//Arrange
result = [{id:0}];
// Act
vm.getAllCounties();
//I think that you also have to do this, but I am not a 100% sure.
scope.$apply();
// Assert
expect(vm.counties).toBe(result); //assert to whatever is resolved in the spyOn function
});
});
}
I am following this video tutorial and its source is here.
I am trying to apply this test
Here is my test
describe("InStudentController", function () {
beforeEach(module("eucngts"));
var inStudentsController;
var MyInStudentsService;
var $scope;
var $q;
var deferred;
beforeEach(function () {
MyInStudentsService =
{
getInStudents: function () {
deferred = $q.defer();
return deferred.promise;
}
};
});
beforeEach(inject(function ($controller, $rootScope, _$q_) {
$q = _$q_;
$scope = $rootScope.$new();
inStudentsController = $controller('InStudentsController', {
service: MyInStudentsService
});
}));
it("should request list of inStudents", function () {
spyOn(MyInStudentsService, "getInStudents").and.callThrough();
inStudentsController.getPage(); // <-- HERE
//deferred.resolve();
$scope.$root.$digest();
expect(MyInStudentsService.getInStudents).toHaveBeenCalled();
});
});
Here is relevant controller code:
InStudentsController.prototype.getPage = function (criteria) {
var self = this;
self.showGrid = true;
self.service.getInStudents();
};
When I call getPage() on test it calls real service method instead of defined in test.
What am I doing wrong?
EDIT
I don't use scope in my controller here is generated code(I use typescript):
function InStudentsController (service) {
var self = this;
self.service = service;
}
InStudentsController.$inject = ['InStudentsService'];
angular.module("eucngts").controller("InStudentsController", InStudentsController);
According to your latest update it is clear that the name of dependency is used wrong in the test. It must be InStudentsService instead of service. When using $inject property of controller constructor only that name matters, not the formal parameter name in function. That makes minification possible
inStudentsController = $controller('InStudentsController', {
InStudentsService: MyInStudentsService
});
Right now you're not injecting a scope into the controller. I think this:
$scope = $rootScope.$new();
inStudentsController = $controller('InStudentsController', {
service: MyInStudentsService
});
Should be this:
$scope = $rootScope.$new();
$scope.service = MyInStudentsService
inStudentsController = $controller('InStudentsController', {
$scope: $scope
});
But it seems odd passing the service in on the scope. Instead, you should be declaring the controller something like this:
angular.module('myApp')
.controller('InStudentsController', function ($scope, InStudentsService) {
...
});
And then the service would be injected like so:
$scope = $rootScope.$new();
inStudentsController = $controller('InStudentsController', {
$scope: $scope,
InStudentsService: MyInStudentsService
});
I have been searching for a couple of hours now for a solution for this but I just can't make it work.
I have a Controller defined as:
(function () {
'use strict';
angular.module('spaSkeleton.parCCP')
.controller('ParCCPCtrl', function ($scope, $mdToast, AnosLetivosService, UnidadesOrganicasService, CursosService, RelatoriosService, PareceresService) {
//my code
and I want to test this controller, but i have all this Services that I have to inject.
One of the Services looks like this:
var app = angular.module('sigq.anosLetivos', []);
app.service('AnosLetivosService', function (Restangular) {
this.getAnosLetivos = function () {
return Restangular.all("anos-letivos").getList({"sort": "ano_inicio"});
};
});
and in my test file I have this:
describe('Parecer Controllers', function(){
beforeEach(module('spaSkeleton.parCCP'));
beforeEach(function() {
module('namespace.anosLetivos');
module('namespace.unidadesOrganicas');
module('namespace.cursos');
module('namespace.relatorios');
module('namespace.pareceres');
module('namespace.landingPage');
});
describe('Parecer Ctrl', function(){
var scope, ctrl, $httpBackend;
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET(...).respond(...);
scope = $rootScope.$new();
ctrl = $controller('ParCtrl', {$scope: scope});
}));
});
});
I would like some help on how to inject these services into the controller so i can test it. I already tried a lot of stuff.
https://docs.angularjs.org/tutorial/step_11 this looks easy but does not work, he doesn't even inject stuff or does he? I know in the tutorial works but I don't know how and why and I can't make it work on my project.
Any help is welcome :D
so I solved my problem, the problem was that the services had a module that i had to inject in the test that i wasn't seeing.
describe('Parecer Controllers', function(){
beforeEach(module('spaSkeleton.parCCP'));
beforeEach(function() {
module('sigq.anosLetivos');
module('sigq.unidadesOrganicas');
module('sigq.cursos');
module('sigq.relatorios');
module('sigq.pareceres');
module('restangular');
module('ngMaterial');
});
var $scope;
var $controller;
var $mdToast, AnosLetivosService, UnidadesOrganicasService, CursosService, RelatoriosService, PareceresService, Restangular;
beforeEach(inject(function(_$controller_, _$q_, _AnosLetivosService_, _UnidadesOrganicasService_, _CursosService_,
_RelatoriosService_, _PareceresService_, _Restangular_, _$mdToast_) {
$scope = {};
$mdToast = _$mdToast_;
Restangular = _Restangular_;
$controller = _$controller_;
AnosLetivosService = _AnosLetivosService_;
UnidadesOrganicasService = _UnidadesOrganicasService_;
CursosService = _CursosService_;
RelatoriosService = _RelatoriosService_;
PareceresService = _PareceresService_;
$controller('ParCCPCtrl',
{
'$scope': $scope,
'AnosLetivosService': AnosLetivosService,
'UnidadesOrganicasService': UnidadesOrganicasService,
'CursosService': CursosService,
'RelatoriosService': RelatoriosService,
'PareceresService': PareceresService,
'$mdToast': $mdToast
});
}));
it('should make Blog menu item active.', function() {
expect(1).toEqual(1);
});
});
so i need all this code to test my controller xD
I'm using Jasmine to unit test an Angular controller which has a method that runs asynchronously. I was able to successfully inject dependencies into the controller but I had to change up my approach to deal with the async because my test would run before the data was loaded. I'm currently trying to spy on the mock dependency and use andCallThrough() but it's causing the error TypeError: undefined is not a function.
Here's my controller...
myApp.controller('myController', function($scope, users) {
$scope.user = {};
users.current.get().then(function(user) {
$scope.user = user;
});
});
and my test.js...
describe('myController', function () {
var scope, createController, mockUsers, deferred;
beforeEach(module("myApp"));
beforeEach(inject(function ($rootScope, $controller, $q) {
mockUsers = {
current: {
get: function () {
deferred = $q.defer();
return deferred.promise;
}
}
};
spyOn(mockUsers.current, 'get').andCallThrough();
scope = $rootScope.$new();
createController = function () {
return $controller('myController', {
$scope: scope,
users: mockUsers
});
};
}));
it('should work', function () {
var ctrl = createController();
deferred.resolve('me');
scope.$digest();
expect(mockUsers.current.get).toHaveBeenCalled();
expect(scope.user).toBe('me');
});
});
If there is a better approach to this type of testing please let me know, thank you.
Try
spyOn(mockUsers.current, 'get').and.callThrough();
Depends on the version you have used: on newer versions andCallThroungh() is inside the object and.
Here the documentation http://jasmine.github.io/2.0/introduction.html
We have few methods in Angular Controller, which are not on the scope variable.
Does anyone know, how we can execute or call those methods inside Jasmine tests?
Here is the main code.
var testController = TestModule.controller('testController', function($scope, testService)
{
function handleSuccessOfAPI(data) {
if (angular.isObject(data))
{
$scope.testData = data;
}
}
function handleFailureOfAPI(status) {
console.log("handleFailureOfAPIexecuted :: status :: "+status);
}
// this is controller initialize function.
function init() {
$scope.testData = null;
// partial URL
$scope.strPartialTestURL = "partials/testView.html;
// send test http request
testService.getTestDataFromServer('testURI', handleSuccessOfAPI, handleFailureOfAPI);
}
init();
}
Now in my jasmine test, we are passing "handleSuccessOfAPI" and "handleFailureOfAPI" method, but these are undefined.
Here is jasmine test code.
describe('Unit Test :: Test Controller', function() {
var scope;
var testController;
var httpBackend;
var testService;
beforeEach( function() {
module('test-angular-angular');
inject(function($httpBackend, _testService_, $controller, $rootScope) {
httpBackend = $httpBackend;
testService= _testService_;
scope = $rootScope.$new();
testController= $controller('testController', { $scope: scope, testService: testService});
});
});
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequest();
});
it('Test controller data', function (){
var URL = 'test server url';
// set up some data for the http call to return and test later.
var returnData = { excited: true };
// create expectation
httpBackend.expectGET(URL ).respond(200, returnData);
// make the call.
testService.getTestDataFromServer(URL , handleSuccessOfAPI, handleFailureOfAPI);
$scope.$apply(function() {
$scope.runTest();
});
// flush the backend to "execute" the request to do the expectedGET assertion.
httpBackend.flush();
// check the result.
// (after Angular 1.2.5: be sure to use `toEqual` and not `toBe`
// as the object will be a copy and not the same instance.)
expect(scope.testData ).not.toBe(null);
});
});
I know this is an old case but here is the solution I am using.
Use the 'this' of your controller
.controller('newController',['$scope',function($scope){
var $this = this;
$this.testMe = function(val){
$scope.myVal = parseInt(val)+1;
}
}]);
Here is the test:
describe('newDir', function(){
var svc,
$rootScope,
$scope,
$controller,
ctrl;
beforeEach(function () {
module('myMod');
});
beforeEach(function () {
inject(function ( _$controller_,_$rootScope_) {
$controller = _$controller_;
$rootScope = _$rootScope_;
$compile = _$compile_;
$scope = $rootScope.$new();
ctrl = $controller('newController', {'$rootScope': $rootScope, '$scope': $scope });
});
});
it('testMe inc number', function() {
ctrl.testMe(10)
expect($scope.myVal).toEqual(11);
});
});
Full Code Example
As is you won't have access to those functions. When you define a named JS function it's the same as if you were to say
var handleSuccessOfAPI = function(){};
In which case it would be pretty clear to see that the var is only in the scope within the block and there is no external reference to it from the wrapping controller.
Any function which could be called discretely (and therefore tested) will be available on the $scope of the controller.