I am trying to test a small controller written in AngularJS using Jasmin.
(function() {
'use strict';
angular
.module('bsp.account')
.controller('Account', Account);
/* #ngInject */
function Account(userService, accountService) {
var vm = this;
vm.title = 'Account';
vm.username = userService.getUsername();
vm.showPasswordModal = accountService.showPasswordModal;
vm.showLogoutModal = accountService.showLogoutModal;
}
})();
I want to test vm.username ,vm.showPersonModal and vm.showLogoutModal.these are all the reference to the service injected in the controller.
I am fairly new and slowly trying to build my concept in testing.
Below is the piece of test cases running now,
describe('Account', function() {
var scope, controller, userServiceMock,accountServiceMock;
beforeEach(module('bsp'));
beforeEach(function() {
userServiceMock = {
getUsername: function(){}
};
accountServiceMock = {
showPasswordModal :function(){}
};
});
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller('Account', {
'userService': userServiceMock,
'accountService':accountServiceMock
});
}));
describe('testing Title',function(){
it('checkTitle', function(){
expect(controller.title).toEqual('Account');
});
});
});
Thank You for all your suggestions
Only problems I can see is that
You're bootstrapping the wrong module (should be 'bsp.account' instead of 'bsp')
You've not provided a means to test that your service methods are called
You can address the latter using spies. For example
describe('Account', function() {
var controller, userServiceMock, accountServiceMock;
beforeEach(function() {
module('bsp.account');
userServiceMock = jasmine.createSpyObj('userService', ['getUsername']);
userServiceMock.getUsername.and.returnValue('testUser');
accountServiceMock = jasmine.createSpyObj('accountService', ['showPasswordModal', 'showLogoutModal']);
inject(function($controller) {
controller = $controller('Account', {
userService: userServiceMock,
accountService: accountServiceMock
});
});
});
it('assigns values from services', function() {
expect(userServiceMock.getUsername).toHaveBeenCalled();
expect(controller.username).toEqual('testUser');
expect(controller.showPasswordModal).toBe(accountServiceMock.showPasswordModal);
expect(controller.showLogoutModal).toBe(accountServiceMock.showLogoutModal);
});
});
Related
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 am currently testing a controller in mocha. The controller has an activate function which should fire success/failure based on the response. I cannot get the failure or success functions to fire during my tests.
viewController.js:
(function() {
'use strict';
angular
.module('app')
.controller('viewCtrl', viewCtrl);
function viewCtrl(Service) {
vm.Service = Service;
activate();
function activate() {
vm.Service.get().then(success, failure);
function success(data) {
if (!data || data == 401) {
failure(data);
}
}
function failure(error) {
if (error) {
console.error("Loading question failed:", error);
vm.Service.set();
}
}
}
}
})();
viewControllerTest.js:
describe('question_view_controller', function() {
var httpBackend, controller;
var expect = chai.expect;
var assert = chai.assert;
var Service = {};
var createController;
beforeEach(function(){
angular.mock.module('ui.router');
angular.mock.module('question');
Service = {
set : sinon.stub(),
get : sinon.stub().returns(Promise.reject({error:{}}));
}
})
beforeEach(inject(function($httpBackend,$controller,$q){
httpBackend = $httpBackend;
createController = function(){
return $controller('ViewCtrl', {
$scope: scope,
Service: Service
});;
}
}));
afterEach(function(){
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequest();
});
describe('activate', function () {
describe('get.then() error', function(){
beforeEach(function(){
Service.get.returns(Promise.reject({error:{}}))
})
it('should do nothing and setFailedQuestion should be called once', function(){
vm = createController();
scope.$digest();
expect(vm.Service.set.callCount).to.equal('1');
})
})
});
});
If anyone could point out my mistake or provide any insight that would be great. Anymore questions please ask.
UPDATE:
Edited code to reflect danday74's answer. Still not working.
UPDATE:
Edited code to reflect danday74's comment. Still not working.
you will need to call scope digest. you will need to inject $rootScope and then ...
vm = createController();
$rootScope.$digest();
expect(vm.Service.set.callCount).to.equal('1');
$digest() causes the THEN blocks to be executed.
similar approach to $httpBackend.flush() if you have ever used that.
In my Angular app, UI router resolves a promise into the controller. When trying to test this controller, Karma is complaining about an unknown provider. How do I inject a fake object into the test to represent this resolve object.
My app's code looks something like:
angular.module('myapp')
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('tab.name', {
...
resolve: {
allTemplates: function(Templates) {
return Templates.all().then(function(templates) {
return templates;
});
}
}
})
})
.controller('QueriesCtrl', function(allTemplates, UserQuery) {
var vm = this;
vm.queries = allTemplates;
vm.goToUrl = function(index, data) {
var processedUrl = UserQuery.process(data, vm.queryTyped[index]);
UserQuery.goToUrl(processedUrl);
};
});
When trying to run tests I get the error
Unknown provider: allTemplatesProvider <- allTemplates <- QueriesCtrl
I've tried creating a spy and injecting it, but this does not work. Here's my test at the moment:
describe('Unit: queriesCtrl', function() {
var controller,
scope,
UserQuery;
beforeEach(function() {
module('myapp');
inject(function($injector) {
UserQuery = $injector.get('UserQuery');
allTemplates = jasmine.createSpyObj('allTemplates', [{a:1}, {a:2}, {b:3}]);
});
});
describe('goToUrl', function() {
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller('QueriesCtrl as ctrl', {
'$scope': scope
});
}));
it('should call UserQuery.process()', function() {
spyOn(UserQuery, 'process');
scope.ctrl.goToUrl();
expect(UserQuery.process).toHaveBeenCalled();
});
});
});
Since there is no route involved in unit test you would have to inject the allTemplates as a normal object with $controller function. Can you try:
controller = $controller('QueriesCtrl as ctrl', {
'$scope': scope,
'allTemplates':allTemplates
});
Else you can use the $provide API to create a dummy service.
module(function ($provide) {
$provide.value("allTemplates", {[{a:1}, {a:2}, {b:3}]});
Do it first thing in your beforEach block.
After much reading, it seems that the recommended way to call a web service from an AngularJS controller is to use a factory and return a promise from that.
Here I have a simple factory which calls a sample API.
myApp.factory('MyFactory', ['$http',function($http) {
var people = {
requestPeople: function(x) {
var url = 'js/test.json';
return $http.get(url);
}
};
return people;
}]);
And this is how I call it in the controller
myApp.controller('MyCtrl1', ['$scope', 'MyFactory', function ($scope, MyFactory) {
MyFactory.requestPeople(22).then(function(result) {
$scope.peopleList = result;
});
}]);
While it works fine, I would like to be able to mock the result that is passed in when then is called. Is this possible?
My attempt so far has produced nothing. This is my attempt:
//Fake service
var mockService = {
requestPeople: function () {
return {
then: function () {
return {"one":"three"};
}
}
}
};
//Some setup
beforeEach(module('myApp.controllers'));
var ctrl, scope;
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('MyCtrl1', { $scope: scope, MyFactory: mockService });
}));
//Test
it('Event Types Empty should default to false', inject(function () {
expect(scope.peopleList.one).toBe('three');
}));
The error that I get when running this in karma runner, is
TypeError: 'undefined' is not an object (evaluating 'scope.peopleList.one')
How can I get this test working with my mocked data?
I don't think $httpBackend is what you're after here, you want the whole factory to be mocked without it having a dependency on $http?
Take a look at $q, in particular the code sample under the Testing header. Your issue might be resolved with code that looks like this:
'use strict';
describe('mocking the factory response', function () {
beforeEach(module('myApp.controllers'));
var scope, fakeFactory, controller, q, deferred;
//Prepare the fake factory
beforeEach(function () {
fakeFactory = {
requestPeople: function () {
deferred = q.defer();
// Place the fake return object here
deferred.resolve({ "one": "three" });
return deferred.promise;
}
};
spyOn(fakeFactory, 'requestPeople').andCallThrough();
});
//Inject fake factory into controller
beforeEach(inject(function ($rootScope, $controller, $q) {
scope = $rootScope.$new();
q = $q;
controller = $controller('MyCtrl1', { $scope: scope, MyFactory: fakeFactory });
}));
it('The peopleList object is not defined yet', function () {
// Before $apply is called the promise hasn't resolved
expect(scope.peopleList).not.toBeDefined();
});
it('Applying the scope causes it to be defined', function () {
// This propagates the changes to the models
// This happens itself when you're on a web page, but not in a unit test framework
scope.$apply();
expect(scope.peopleList).toBeDefined();
});
it('Ensure that the method was invoked', function () {
scope.$apply();
expect(fakeFactory.requestPeople).toHaveBeenCalled();
});
it('Check the value returned', function () {
scope.$apply();
expect(scope.peopleList).toBe({ "one": "three" });
});
});
I've added some tests around what $apply does, I didn't know that until I started playing with this!
Gog
I am trying to write a simple test using Jasmine. The test checks if $scope.testFlag is set to false.
Here is my test code
describe('Abc Controller', function() {
var $scope = null;
var ctrl = null;
//you need to indicate your module in a test
beforeEach(module('myApp'));
beforeEach(inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
ctrl = $controller('abcController', {
$scope: $scope
});
}));
// test 1
it('testFlag should be set to False', function() {
expect( $scope.testFlag).toEqual(false);
});
});
But for some reason I get this error:
Error: Unknown provider: ConfigProvider <- Config <- collectionMetaFactory
Here is how my application's app.js looks like which I am including in testRunner.html
var app = angular.module('myApp')
app.constant('Config',
{
baseURL : serviceURL,
httpTimeout : 3600000 // 1 minute
});
app.config(function($logProvider) {
$logProvider.debugEnabled(true);
});
What am I missing?
Here is controller's snippet
app.controller('abcController', function ($scope, $log,abcFactory, Config) {
$scope.testFlag = false;
// more code follows
});
Let me know if you need see more of the application's code (like factory, services and controller)
You haven't provided your test with Config, so it doesn't recognize what Config is and that's why you are getting this error.
A solution would be to inject Config into your test by using the $provide module. Here's how:
describe('Abc Controller', function() {
var $scope = null;
var ctrl = null;
var Config = {
baseURL : 'someURL',
httpTimeout : 3600000 // 1 minute
};
beforeEach(function(){
beforeEach(module('myApp'));
module(function (_$provide_) {
$provide = _$provide_;
$provide.value('Config', Config);
});
});
//you need to indicate your module in a test
beforeEach(inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
ctrl = $controller('abcController', {
$scope: $scope
});
}));
// test 1
it('testFlag should be set to False', function() {
expect( $scope.testFlag).toEqual(false);
});
});
This should run the tests successfully.
Hope this helps others facing a similar problem.