My controller is:
angularMoonApp.controller('SourceController', ['$scope', '$rootScope', '$routeParams', 'fileService', function ($scope, $rootScope, $routeParams, fileService) {
$scope.init = function() {
$rootScope.currentItem = 'source';
fileService.getContents($routeParams.path).then(function(response) {
$scope.contents = response.data;
$scope.fileContents = null;
if(_.isArray($scope.contents)) {
// We have a listing of files
$scope.breadcrumbPath = response.data[0].path.split('/');
} else {
// We have one file
$scope.breadcrumbPath = response.data.path.split('/');
$scope.breadcrumbPath.push('');
$scope.fileContents = atob(response.data.content);
fileService.getCommits(response.data.path).then(function(response) {
$scope.commits = response.data;
});
}
});
}
$scope.init();
}]);
My test is pretty simple:
(function() {
describe('SourceController', function() {
var $scope, $rootScope, $httpBackend, createController;
beforeEach(module('angularMoon'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
var $controller = $injector.get('$controller');
createController = function() {
return $controller('SourceController', {
'$scope': $scope
});
};
}));
it("should set the current menu item to 'source'", function() {
createController();
$scope.init();
expect($rootScope.currentItem).toBe('source');
});
it("should get the contents of the root folder", function() {
createController();
$scope.init();
// NOT SURE WHAT TO DO HERE!
});
});
})();
I want to test that the fileService had it's getContents function called and mock a response so that I can test the two scenarios (if is array and if isn't`)
I would recommend using Jasmine spies for this.
Here is an example that might help. I usually put the spyOn call in the beforeEach.
var mockedResponse = {};
spyOn(fileService, "getContents").andReturn(mockedResponse);
In the 'it' part:
expect(fileService.getContents).toHaveBeenCalled();
To get the response, just call the method in your controller that calls the fileService method. You may need to manually run a digest cycle too. Snippet from one of my tests:
var testOrgs = [];
beforeEach(inject(function(coresvc) {
deferred.resolve(testOrgs);
spyOn(coresvc, 'getOrganizations').andReturn(deferred.promise);
scope.getAllOrganizations();
scope.$digest();
}));
it("getOrganizations() test the spy call", inject(function(coresvc) {
expect(coresvc.getOrganizations).toHaveBeenCalled();
}));
it("$scope.organizations should be populated", function() {
expect(scope.allOrganizations).toEqual(testOrgs);
expect(scope.allOrganizations.length).toEqual(0);
});
deferred in this case is a promise created with $q.defer();
You can create a spy and verify only that fileService.getContents is called, or either verify extra calls (like promise resolution) by making the spy call through. Probably you should also interact with httpBackend since you may need to flush the http service (even though you use the mock service).
(function() {
describe('SourceController', function() {
var $scope, $rootScope, $httpBackend, createController, fileService;
beforeEach(module('angularMoon'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
// See here
fileService = $injector.get('fileService');
spyOn(fileService, 'getContents').andCallThrough();
var $controller = $injector.get('$controller');
createController = function() {
return $controller('SourceController', {
'$scope': $scope
'fileService': fileService
});
};
}));
it("should get the contents of the root folder", function() {
createController();
$scope.init();
expect(fileService.getContents).toHaveBeenCalled();
});
});
})();
You can also add expectations to what happens inside the callback but you should issue a httpBackend.flush() before.
Related
I am trying to do unit test of my angular app with karma. I am getting some error. Am i missing something? A
This my controller
(function () {
"use strict"
angular
.module("myApp")
.controller("userCtrl",['$scope', '$state', 'userService', 'appSettings','md5','currentUser','$rootScope',
function ($scope, $state, userService, appSettings,md5,currentUser, $rootScope) {
$scope.login = function() {
$scope.loading = true;
if($scope.password != null){
var user ={
username:$scope.username,
password:md5.createHash($scope.password)
}
var getData = userService.login(user);
getData.then(function (response) {
console.log(response);
$scope.loading = false;
currentUser.setProfile(user.username, response.data.sessionId);
$state.go('videos');
}, function (response) {
console.log(response.data);
});
}else{
$scope.msg = "Password field is empty!"
}
}
}])
}());
This is my test codes
'use strict';
describe('userCtrl', function() {
beforeEach(module('myApp'));
var scope, userCtrl, apiService,q, deferred, currentUser;
describe('$scope.login', function(){
beforeEach(function(){
apiService = {
login: function () {
deferred = q.defer();
return deferred.promise;
};
};
});
beforeEach(inject(function($controller, $rootScope, $q, _currentUser_){
var user ={name:'ali',password:'password'};
scope = $rootScope.$new();
q = $q;
// The injector unwraps the underscores (_) from around the parameter names when matching
userCtrl = $controller('userCtrl', {
$scope:scope,
userService:apiService
});
//userService = _userService_;
currentUser = _currentUser_;
}));
it('should call user service login', function() {
spyOn(apiService, 'login').and.callThrough();
scope.login();
deferred.resolve(user);
expect(apiService.login).toHaveBeenCalled();
});
it('checks the password field', function() {
scope.login();
expect(scope.msg).toEqual('Password field is empty!');
});
});
});
And i am getting this error
enter image description here
If you have to test controller then use to spyon for service method and in case of service then use HttpBackend
describe('Testing a Controller that uses a Promise', function() {
var $scope;
var $q;
var deferred;
beforeEach(module('search'));
beforeEach(inject(function($controller, _$rootScope_, _$q_, searchService) {
$q = _$q_;
$scope = _$rootScope_.$new();
// We use the $q service to create a mock instance of defer
deferred = _$q_.defer();
// Use a Jasmine Spy to return the deferred promise
spyOn(searchService, 'search').and.returnValue(deferred.promise);
// Init the controller, passing our spy service instance
$controller('SearchController', {
$scope: $scope,
searchService: searchService
});
}));
it('should resolve promise', function() {
// Setup the data we wish to return for the .then function in the controller
deferred.resolve([{
id: 1
}, {
id: 2
}]);
// We have to call apply for this to work
$scope.$apply();
// Since we called apply, not we can perform our assertions
expect($scope.results).not.toBe(undefined);
expect($scope.error).toBe(undefined);
});
});
This for same using spyon for service method then use $appy method to make it work.
i have a problem when mocking a data service promise.
I use AngularJS and Jasmine 2.2.0
My Code is:
Controller
app.controller('homeController', ['$scope', 'ngAppSettings', 'homeViewModel', 'storageService', 'homeService',
function ($scope, ngAppSettings, homeViewModel, storageService, homeService) {
$scope.applicationName = ngAppSettings.applicationName;
$scope.model = homeViewModel;
storageService.getStorageAuth().then(function (data) {
$scope.model.userName= data.name;
}, function (err) {
alert(err.errors[0].message);
});
}]);
Service
app.service('storageService', ['$q', 'ngAppSettings', 'localStorageService',
function ($q, ngAppSettings, localStorageService) {
this.getStorageAuth = function () {
var deferred = $q.defer();
var userAuth = localStorageService.get(ngAppSettings.storageUser);
if (userAuth) {
data = userAuth;
}
deferred.resolve(data);
return deferred.promise;
};
}]);
Test Spec
describe('Controllers: homeController', function () {
var $rootScope;
var $scope;
var ctrl;
var $q;
var deferred;
var storageService;
beforeEach(module('IspFrontEndTemplateApp'));
beforeEach(inject(function (_$q_, _storageService_) {
$q = _$q_;
storageService = _storageService_;
deferred = $q.defer();
spyOn(storageService, "getStorageAuth").and.returnValue(deferred.promise);
}));
beforeEach(inject(function (_$rootScope_, $controller) {
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
ctrl = $controller('homeController', { $scope: $scope });
}));
it('UserName is: UsuarioTeste', function () {
deferred.resolve({
isAuth: true,
userName: "UsuarioTeste",
name: "UsuarioTeste"
});
expect($scope.model.userName).toBe('UsuarioTeste');
});
});
The error is: Expected '' to be 'UsuarioTeste'.
I need test my model properties in controller but the value is not refreshed
You need to $digest one more time in your test:
$scope.$digest();
expect($scope.model.userName).toBe('UsuarioTeste'); // will work
The reason is that promises are resolved asynchronously in the sense that a promise never calls its success or error callbacks within the same $apply it has been created in, even if it has been resolved. Code could be hard to read if you create a promise in the beginning of your function and the effect of the resolved promise would be already observable a few lines later.
I just start with tests in AngularJS. Please help me to fix it.
My cript
angular.module('test', [])
.controller('ctrl', ['$scope', 'svc', function ($scope, svc) {
$scope.data = [];
svc.query()
.then(function (data) {
$scope.data = data;
});
}]);
and test spec
describe('ctrl', function () {
var ctrl, scope, svc, def, data = [{name: 'test'}];
beforeEach(module('test'));
beforeEach(inject(function($controller, $rootScope, $q) {
svc = {
query: function () {
def = $q.defer();
return def.promise;
}
};
var a=jasmine.createSpy(svc, 'query');
scope = $rootScope.$new();
controller = $controller('ctrl', {
$scope: scope,
svc: svc
});
}));
it('should assign data to scope', function () {
def.resolve(data);
scope.$digest();
expect(svc.query).toHaveBeenCalled();
expect(scope.data).toBe(data);
});
});
It fail:Error: Expected a spy, but got Function. in http://cdn.jsdelivr.net/jasmine/2.0.0/jasmine.js (line 2125). Can you help me
You are getting that error because its failing on expect method. expect method is expecting a spy to be passed in but its not. To fix this problem do:
spyOn(svc, 'query').andCallThrough();
You're creating a spy using createSpy(), which returns a function you can spy on, but you nere use it. You're making your life more complex than it should be. Just let angular inject the real service, and spy on its query() function. Also, use $q.when() to create a resolved promise.
describe('ctrl', function () {
var scope, svc;
var data = [{name: 'test'}];
beforeEach(module('test'));
beforeEach(inject(function($controller, $rootScope, $q, _svc_) {
svc = _svc_;
spyOn(svc, 'query').andReturn($q.when(data));
scope = $rootScope.$new();
$controller('ctrl', {
$scope: scope,
});
}));
it('should assign data to scope', function () {
scope.$digest();
expect(svc.query).toHaveBeenCalled();
expect(scope.data).toBe(data);
});
});
I'd like to know which would be the best way to test functions that returns nothing(just changes a field value) and contains an async call.
This the AngularJS controller I want to test, the service I call returns a promise(always returns {name:"John"}):
app.controller('MyCtrl', function($scope, AsyncService) {
$scope.greeting = "";
$scope.error =
$scope.sayHello = function() {
AsyncService.getName().then(
function(data){
$scope.saludo = "Hello " + data.name;
},
function(data){
$scope.error = data;
}
);
};
});
This would be the spec if the sayHello function did not contain an async call, but it always fails because scope.greeting is always empty.
describe('Test My Controller', function() {
var scope, $httpBackend;
// Load the module with MainController
//mock Application to allow us to inject our own dependencies
beforeEach(angular.mock.module('app'));
//mock the controller for the same reason and include $rootScope and $controller
beforeEach(angular.mock.inject(function($rootScope, $controller,_$httpBackend_){
//Mock the service to always return "John"
$httpBackend = _$httpBackend_;
$httpBackend.when('POST', 'http://localhost:8080/greeting').respond({name: "John"});
//create an empty scope
scope = $rootScope.$new();
//declare the controller and inject our empty scope
$controller('MyCtrl', {$scope: scope});
}));
it('$scope.greeting should get filled after sayHello', function() {
expect(scope.greeting).toEqual("");
scope.sayHello();
expect(scope.greeting).toEqual("Hello John");
});*/
});
How would I make this spec to handle the async call? I don't really understand how and where to use the "done" flag of Jasmine 2.0.
Use $q.defer() to return a promise from the getName function in a mock of your service. Then pass the mocked into the dependancies when your controller is created:
beforeEach(inject(function($controller, _$rootScope_, $q) {
$rootScope = _$rootScope_;
deferred = $q.defer();
asyncService = {
getName: function () {
}
};
spyOn(asyncService, 'getName').and.returnValue(deferred.promise);
$scope = $rootScope.$new();
createController = function() {
return $controller('MyCtrl', { $scope: $scope, AsyncService: asyncService } );
};
}));
Then after you call $scope.hello() call deferred.resolve(data)l where data is the data that you want returned from your service in the promise. Then call $rootScope.$digest();
it('$scope.saludo should get filled after sayHello', function() {
//Arrange
var controller = createController();
var data = {
name: 'John'
};
//Act
$scope.sayHello();
deferred.resolve(data);
$rootScope.$digest();
//Assert
expect($scope.saludo).toEqual('Hello ' + data.name);
});
Plunkr
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.