Angular Test In Jasmine Promises Won't Digest - javascript

I am writing my first Angular test, in Jasmine, and I cannot seem to get a promise to digest in a mock service.
Here is my controller (which is being tested) and the resource it uses:
'use strict';
angular.module('myApp.login', [
'ngCookies',
'ngResource'
])
/*Controllers*/
.controller('login', ['$scope', '$rootScope', '$cookies', 'LoginService', function ($scope, $rootScope, $cookies, LoginService) {
$scope.user = {};
$scope.user.username = "";
$scope.user.password = "";
var loginError = function () {
console.log("failure logging in")
};
var loginSuccess = function (result) {
$cookies.put('authToken', result.token);
$rootScope.loggedIn = true;
};
$scope.login = function () {
var credentials = {username: $scope.user.username, password: $scope.user.password};
var tokenPromise = LoginService.authenticate(credentials);
console.log(tokenPromise);
tokenPromise.$promise.then(loginSuccess, loginError);
}
}])
/*Resources*/
.factory('LoginService', ['$resource', 'apiUrl', function ($resource, apiUrl) {
return $resource(
apiUrl + 'auth/token/',
{},
{
authenticate: {
method: "POST"
}
}
)
}]);
Below is the current version of the test.
describe('myApp.login', function () {
var createController, scope, deferred, LoginService;
var successToken = {'token': 'tokenGoesHere'}
beforeEach(inject(function($rootScope, $controller, $injector, $q, $timeout) {
var cookies = $injector.get('$cookies');
scope = $rootScope.$new();
LoginService = { authenticate: function(user) {
deferred = $q.defer();
$timeout(function(){
deferred.resolve(successToken);
$rootScope.$apply();
$rootScope.$digest();
}, 1000);
$timeout.flush();
return deferred.promise;
}};
return $controller('login', {
'$scope': scope,
'$cookies': cookies,
'LoginService': LoginService
});
};
}));
it('Should set cookie with returned JWT', function() {
var controller = new createController();
scope.user.username = 'username';
scope.user.password = 'password';
scope.login();
});
});
I've tried a $timeout, setTimeout, spyOn, and a few others, but nothing I try returns the promise in the state that the controller expects, and I end up getting the following stacktrace:
PhantomJS 1.9.8 (Linux 0.0.0) myApp.login Should set cookie with returned JWT FAILED
TypeError: 'undefined' is not an object (evaluating 'tokenPromise.$promise.then')
at /home/anton/git/persist-ng/app/login/login.js:53
at /home/anton/git/persist-ng/app/login/login_test.js:42
PhantomJS 1.9.8 (Linux 0.0.0): Executed 1 of 1 (1 FAILED) ERROR (0.041 secs / 0.007 secs)
Any help is appreciated, even if the issue actually lives in the code itself, not the test.

Related

Error: Expected undefined to equal in karma

Test Case File
describe('homeController', function() {
beforeEach(module('moduleInjectionApp'));
var $controller;
var $rootScope;
beforeEach(inject(function(_$controller_, _$rootScope_) {
$controller = _$controller_('homeController', {'$scope': scope});
$rootScope = _$rootScope_;
}));
describe('$scope.ID', function() {
it('Check the scope object', function() {
var $scope = {};
expect($scope.ID).toEqual(5);
});
});
});
Controller File
angular.module('moduleInjectionApp').controller('homeController', homeController)
homeController.$inject = ["$scope", "$rootScope", "$location"];
function homeController($scope, $rootScope, $location) {
console.log("entered homeController")
$scope.ID = 5;
$rootScope.loginObj = JSON.parse(localStorage.getItem('login_data'));
}
Error
Error: Expected undefined to equal 5.
at <Jasmine>
at UserContext.<anonymous> (WebContent/test/home/homeSpec.js:14:31)
at <Jasmine>
Chrome 75.0.3770 (Windows 10.0.0): Executed 1 of 1 (1 FAILED) (0.036 secs / 0.012 secs)
TOTAL: 1 FAILED, 0 SUCCESS
Try
describe('homeController', function() {
beforeEach(module('moduleInjectionApp'));
var $controller;
beforeEach(inject(function(_$controller_){
$controller = _$controller_;
}));
describe('$scope.ID', function() {
it('Check the scope object', function() {
var $scope = {};
var controller = $controller('homeController', { $scope: $scope });
expect($scope.ID).toEqual(5);
});
});
});
When you declare var $scope = {};, you will always get $scope.ID as undefined. You need to do
var $scope = { ID: 5}
Anyways, in unit test, you dont create some values and then expect assertions on it. You validate the values which are already defined or have been modified. Here you were trying to declare and then putting expect (which will always pass)

Unittest of AngularJS Controller with Jasmine and Karma "Cannot read property 'then' of undefined

I am trying to unit-test an angularjs controller and get this error message when running Karma:
Cannot read property 'then' of undefined
What am I doing wrong?
Sorry, it's my first time testing something.
Controller:
angular
.module('my')
.controller('MyCtrl', MyController);
MyController.$inject = ['$scope', 'myFactory'];
function MyController($scope,myFactory) {
$scope.thingy = {};
//[..]
function getThingys() {
myFactory.getThingys(function () {}).then(function (data) {
//SUCCESS
$scope.thingy = data;
});
}
}
Test:
var scope;
var controller;
var mockedMyFactory;
beforeEach(module('my'));
beforeEach(module('my', function ($provide) {
mockedMyFactory = {
getThingys: jasmine.createSpy()
};
$provide.value('myFactory', mockedMyFactory);
}));
beforeEach(inject(function ($controller, $rootScope, myFactory) {
scope = $rootScope.$new();
controller = $controller('MyCtrl', {
$scope: scope, myFactory
});
}));
describe('this', function () {
it('is a dummy spec', function () {
expect(2 + 2).toEqual(4);
});
});
To mock the service:
var $httpBackend;
var myFactory;
beforeEach(angular.mock.inject(function ($injector) {
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
// backend definition common for all tests
myFactory= $injector.get('myFactory');
$httpBackend.when('GET', 'url for call').respond({mock data});
}
afterEach(function () {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});

Spying on service that returns a promise

I have a service that retrieves data with a cache and a fallback to $http, and I'm trying to mock out calls to the service in a controller spec using jasmine spies. However, when I call scope.$digest, the actual service is being called and HTTP call is being made.
I have tried using all combinations of [$rootScope|scope].[$apply|$digest]() and my HTTP calls are still being made. However if I return something other than a promise from my spy, such as a string, I will get an error that then is undefined as a function, so it appears the spy is stubbing the function successfully?
Jasmine Test
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
// Should be called by each test after mocks and spies have been setup
startContoller = function() {
$controller('SearchCtrl', {
$scope: scope,
$stateParams: { q: 'test' }
});
};
}));
it('sets error message when no results', inject(function(QuestionService, $q) {
var deferred;
spyOn(QuestionService, 'search').and.callFake(function() {
deferred = $q.defer();
deferred.reject();
return deferred.promise;
});
startContoller();
scope.$digest();
expect(scope.error).toBe(true);
}));
Controller
.controller('SearchCtrl', ['$scope', '$stateParams', 'QuestionService',
function($scope, $stateParams, QuestionService) {
var query = $stateParams.q;
$scope.page = 1;
QuestionService.search(query, $scope.page).then(function(questions) {
if (questions.length === 0) {
$scope.error = true;
$scope.errorMessage = 'No Results';
} else {
$scope.questions = questions;
}
}, function() {
$scope.error = true;
});
}]);
dmahapatro's comment is the way to go.
As you are retreiving the controller using $controller('SearchCtrl'...), your service is already instanciated by the time your "it" block is executed, so spying on it has no effect.
You should be injecting the mocked service in your $controller call.
Also, no need for your startController() function, as invoking $controller will execute the controller's logic.
var QuestionServiceMock, deferred;
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
QuestionServiceMock = jasmine.createSpyObj('QuestionService', ['search']);
QuestionServiceMock.search.andCallFake(function () {
deferred = $q.defer();
deferred.reject();
return deferred.promise;
});
$controller('SearchCtrl', {
$scope: scope,
$stateParams: { q: 'test' },
QuestionService: QuestionServiceMock
});
}));
it('sets error message when no results', inject(function(QuestionService, $q) {
scope.$apply();
expect(scope.error).toBe(true);
}));

Angularjs how to call asyncron service method and how to use a dependency in listener

I want to use a dependency in listener but the websocket was undefined
$rootScope.$on('websocket.connected', function() {
$websocket.request(.....).then();
});
and a want to call a service method (who depend on asyncron method) when it ready
app.controller('MyCtrl', function(myServ, $log) {
myServ.getInfos();
});
thank you.
Code in jsfiddle http://jsfiddle.net/8DHfY/3/ or here
var app = angular.module('myApp', ['myServ'])
.config(['$websocketProvider', function ($websocketProvider) {
$websocketProvider.setHost('ws://echo.websocket.org/');
}])
.controller('MyCtrl', function(myServ, $log) {
$log.log('I want to call myServ.getInfos() from a controler');
});
angular.module('myServ', ['websocket']).service('myServ', ['$log', '$rootScope', '$websocket', function($log, $rootScope, $websocket) {
$log.error('websocket is %o ... ???', $websocket); // return undefined
$rootScope.$on('websocket.connected', function() {
$log.error('websocket is still %o', $websocket); // return undefined
});
return {
getInfos: function() {
$websocket.request(JSON.stringify({'key': 'value'}));
}
};
}]);
angular.module('websocket', []).provider('$websocket', [function() {
var _this = this;
_this.host = '';
_this.connection = null;
_this.setHost = function(host) {
this.host = host;
return this;
};
_this.request = function(request) {
//request method for websocket
};
this.$get = ['$log', '$rootScope', function($log, $rootScope) {
_this.connection = new WebSocket(this.host);
_this.connection.onopen = function(){
$log.log('Websocket connected to %s', _this.host);
$rootScope.$emit('websocket.connected');
};
}];
}]);
Providers invoke the $get function upon injection and use the singleton of whatever is returned from that function.
This means since you do not return anything from the $get function, it uses undefined.
Here's an updated fiddle: http://jsfiddle.net/8DHfY/4/
this.$get = ['$log', '$rootScope', function($log, $rootScope) {
_this.connection = new WebSocket(this.host);
_this.connection.onopen = function(){
$log.log('Websocket connected to %s', _this.host);
$rootScope.$emit('websocket.connected');
};
return _this;
}];

AngularJs unit test - mocked promise not executing "then"

We're unit testing our controllers. We've successfully mocked the call to our REST service layer and verified that it is indeed being called with the given data. Now however we'd like to test that in our controller the execution of the then promise changes the location.path:
controller:
(function () {
app.controller('registerController', ['$scope', '$location', '$ourRestWrapper', function ($scope, $location, $ourRestWrapper) {
$scope.submitReg = function(){
// test will execute this
var promise = $ourRestWrapper.post('user/registration', $scope.register);
promise.then(function(response) {
console.log("success!"); // test never hits here
$location.path("/");
},
function(error) {
console.log("error!"); // test never hits here
$location.path("/error");
}
);
};
$ourRestWrapper.post(url,data) just wraps Restangular.all(url).post(data)..
Our Test:
(function () {
describe("controller: registerController", function() {
var scope, location, restMock, controller, q, deferred;
beforeEach(module("ourModule"));
beforeEach(function() {
restMock = {
post: function(url, model) {
console.log("deferring...");
deferred = q.defer();
return deferred.promise;
}
};
});
// init controller for test
beforeEach(inject(function($controller, $rootScope, $ourRestWrapper, $location, $q){
scope = $rootScope.$new();
location = $location;
q = $q;
controller = $controller('registerController', {
$scope: scope, $location: location, $ourRestWrapper: restMock});
}));
it('should call REST layer with registration request', function() {
scope.register = {data:'test'};
spyOn(restMock, 'post').andCallThrough();
scope.submitReg();
deferred.resolve();
// successfull
expect(restMock.post).toHaveBeenCalledWith('user/registration',scope.register);
expect(restMock.post.calls.length).toEqual(1);
// fail: Expected '' to be '/'.
expect(location.path()).toBe('/');
});
In our console we see "deferring..." and the first two expectations succeed. Why will it not call the then block (i.e. set the location)?
Cache the $rootscope object when you get it from the injector and call $rootScope.$apply() immediately after deferred.resolve().

Categories

Resources