I am new to developing in angular, and am trying to learn how to test angular controllers. The controller I am testing uses $location.seach().something. I looked at the docs for $location, but don't quickly see how I am supposed to mock this in karma/jasmine.
The controller:
rmtg.controller('ErrorCtrl', ['Session', '$location', '$routeParams', '$scope', '$window',
function(Session, $location, $routeParams, $scope, $window) {
console.log('ErrorCtrl(%o, %o, %o)', $location.path(), $location.search(), $routeParams);
$scope.status = $location.search().status;
$scope.message = $location.search().message;
$scope.isAuthorized = (typeof(Session.auth) === 'object');
$scope.signin = function() {
$window.location = '/signin/#/' + $routeParams.origin + (Session.auth ? '?email=' + Session.auth.email : '');
};
}]);
My current spec attempt:
'user strict';
describe('Testing the errorCtrl controller', function(){
beforeEach(module("rmtg"));
var errorCtrl, scope;
beforeEach(inject(function($controller, $rootScope){
scope = $rootScope;
errorCtrl = $controller("ErrorCtrl", {
$scope: scope
});
}));
it('$scope.status should be set to 404 when location is set to 404', function(){
//set the $location.search values so that the scope is correct
$location.search('status', '404');
expect(scope.status).toBe('404');
});
});
And the current error message:
Testing the errorCtrl controller $scope.status should be set to 404 when location is set to 404 FAILED
Expected undefined to be '404'.
at Object. (/Users/adamremeeting/git/mrp-www/app/tests/example.js:20:24)
I'd also really appreciate links to resources on tdd with angular 1.5 and how I mock and stub correctly.
Edit After Answer
So I updated the test as per user2341963 suggestions, and did my best to look through his plunker example, but still don't have a passing test.
the current spec (controller has not changed from above)
'user strict';
describe('ErrorCtrl', function(){
beforeEach(module("rmtg"));
var scope, $location, $controller;
beforeEach(inject(function(_$controller_, _$rootScope_, _$location_){
scope = _$rootScope_.$new();
$location = _$location_
$controller = $_controller_;
}));
describe('$scope.status', function(){
it('should set status to 404', function(){
//set the $location.search values so that the scope is correct
$location.search('status', '404');
//init controller
$controller('ErrorCtrl', {
$scope: scope,
$location: $location
});
expect(scope.status).toBe('404');
});
});
});
But I am getting an error now that $controller is not defined.
You are getting undefined in your test because you are not setting $location anywhere.
Based on your controller, the search parameters must be set before the controller is initialised. See plunker for full example.
describe('testApp', function() {
describe('MainCtrl', function() {
var scope, $location, $controller;
beforeEach(module('myApp'));
beforeEach(inject(function(_$rootScope_, _$controller_, _$location_) {
scope = _$rootScope_.$new();
$location = _$location_;
$controller = _$controller_;
}));
it('should set status to 404', function() {
// Set the status first ...
$location.search('status', '404');
// Then initialise the controller
$controller('MainCtrl', {
$scope: scope,
$location: $location
});
expect(scope.status).toBe('404');
});
});
});
As for resources, so far I've found the angular docs are good enough.
Related
I'm trying to test my simple controller but seems like nothing is working.
the controller:
userCtrlMod.controller('resetCtrl',
['$scope', '$ionicPopup', '$timeout','resetPwd',
function($scope, $ionicPopup, $timeout, resetPwd){
$scope.reset = function(){
$scope.resetPopUp = $ionicPopup.show({
templateUrl:'././templates/popup/reset.html',
scope: $scope
});
}}]);
my test file :
describe("resetCtrl", function () {
var $myScope, $myController, timeout;
beforeEach(module('dbooks.userCtrl'));
beforeEach(inject(function(
_$controller_,
_$rootScope_,
_$timeout_,
$ionicPopup
){
$myController = _$controller_;
$myScope = _$rootScope_;
$myController = $controller('resetCtrl' , {
$scope: $myScope,
$resetPopUp : $ionicPopup
});
}));
it("should have a $scope variable", function() {
//console.log($myScope);
expect($myScope).toBeDefined();
});});
I googled it but i could'nt find any solution, please someone tell me what I'm doing wrong.
the errors :
Uncaught Error: [$injector:unpr] Unknown provider: $ionicPopupProvider <- $ionicPopup
Uncaught Expected undefined to be defined.
at Object.
You don't provide all required dependencies when creating controller in test. You have to provide all dependencies required by the controller:
describe("resetCtrl", function () {
var $myScope, $myController, timeout;
beforeEach(module('dbooks.userCtrl'));
beforeEach(inject(function(
_$controller_,
_$rootScope_,
_$timeout_,
$ionicPopup
){
$myController = _$controller_;
$myScope = _$rootScope_;
var resetPwd = {
someResetmethod: jasmine.createSpy('rese')
};
$myController = $controller('resetCtrl' , {
$scope: $myScope,
$ionicPopup: $ionicPopup,
$timeout: _$timeout_,
resetPwd: resetPwd
});
}));
it("should have a $scope variable", function() {
//console.log($myScope);
expect($myScope).toBeDefined();
});
}
Please note that you can inject mocked objects as dependencies - in above code instead of original resetPwd mocked object with spy as method is injected. The important thing is that you have to provide all dependencies used by your controller and if you inject mocked objects those object of course have to include required methods and properties.
Please try this.
$myScope =__$rootScope_.$new();
First, I never seen Angular and Jasmine until several months ago. So I spend two or three months studying this in the practices of a company, and finally they've sent me to try test a controller/service in Visual Studio Code.
I have this variable in the controller:
vm.option = $state.param.option;
And in the spec.js I create a it with this:
it('"option" should be defined', function () {
expect(ctrl.option).toBeDefined();
});
Previously, I inject in beforeEach a $controller, $rootScope, _$log_, $injector and the service. I need something special for test this variable? I tried inject _$state_ but the message Expected undefined to be defined appears too.
I appreciate all help, and sorry for my bad english.
Edit:
The spec.js :
'use strict';
describe('app/specs/spec.js', function () {
var scope, $log, service, ctrl, state/*, testedStateExample*/;
beforeAll(function () {} );
beforeEach(angular.mock.module('App.moduleExample'));
beforeEach(function () {
module(function ($provide){
$provide.constnt('APP_CONFIG', {});
});
});
beforeEach(angular.mock.inject(function ($controller, $state, $rootScope, _$log_, _service_){
service = _service_;
scope = $rootScope.$new();
$log = _$log_;
$state = $state;
state = { params: { option: 'E' }}
ctrl = $controller('controllerExample', {
$scope: scope,
service: service,
$log: $log
});
//testedStateExample = new ctrl(state);
});
it('"option" should be defined', function () {
expect(state.params).toBeDefined();
});
});
There is typo in your controller: $state doesn't have param property but params.
Besides, you have to also define params.option on injected $state object in your tests because in injected $state it's rather not set so your controller can't read it from $state - but how to do this depends on your code details. state.params are set according to URL and route config but when you test standalone controller there is no URL nor route config and as a result $state.params is empty.
The best way to solve your problem is to mock injected $state:
Lets say that you have following controller:
function MyController($state) {
this.option = $state.params.option
...
}
In your test spec you can mock $state service and pass this mock to your controller as argument:
var mockState = {
params: {
option: 'TEST'
}
}
var testedControllerInstance = new MyController(mockState)
}
...
expect(testedControllerInstance.option).toBe('TEST');
Answer update according to updated question:
You've forgotten to inject state into your controller:
beforeEach(angular.mock.inject(function ($controller, $state, rootScope, _$log_, _service_){
service = _service_;
scope = $rootScope.$new();
$log = _$log_;
var state = { params: { option: 'E' }}
ctrl = $controller('controllerExample', {
$scope: scope,
service: service,
$log: $log,
$state: state
});
});
...
})
;
I am new with Sinon so I wanted to check whether a specific function is being called, this is what I got:
terminalController.controller('CashAcceptorController', [
'PaymentService',
'$rootScope',
'$scope',
'PayingInfo',
'$interval',
'$location',
function (PaymentService, $rootScope, $scope, PayingInfo, $interval, $location) {
PaymentService.start();
....
]);
In tests, I try to check that PaymentService.start() is called on controller instantiation:
describe('CashAcceptorController', function() {
var PaymentService, rootScope, scope, PayingInfo, $interval, $location;
var mySpy = sinon.spy(PaymentService.start());;
beforeEach(module('eshtaPayTerminalApp.controllers'));
beforeEach(module('eshtaPayTerminalApp.services'));
beforeEach(inject(function($controller,
$rootScope, _PaymentService_, _$interval_, _PayingInfo_) {
$interval = _$interval_;
scope = $rootScope.$new();
rootScope = $rootScope.$new();
PaymentService = _PaymentService_;
PayingInfo = _PayingInfo_;
rootScope.serviceNumber = 'm1';
rootScope.phoneNumber = '05135309';
$controller('CashAcceptorController', {
$rootScope : rootScope,
$scope : scope,
$location : $location,
_PaymentService_ : PaymentService,
_$interval_:$interval,
_PayingInfo_:PayingInfo
});
}));
it('should call start paying', function() {
expect(mySpy.callCount).to.equal(1);
});
But this assertion fails. What am I doing wrong? Help please :)
There's a couple of issues with your code
The PaymentService object needs to be assigned before you can spy on it
To add a spy with sinon you need pass the method name as a string eg. sinon.spy(PaymentService, 'start');
I've created a working plunk of the above at http://plnkr.co/edit/AvqS3L?p=preview
Here's the updated test code:
describe('CashAcceptorController', function() {
var PaymentService;
var $controller;
beforeEach(module('eshtaPayTerminalApp.controllers'));
beforeEach(module('eshtaPayTerminalApp.services'));
beforeEach(inject(function(_PaymentService_, _$controller_) {
PaymentService = _PaymentService_;
$controller = _$controller_;
}));
it('should call start paying', function() {
var mySpy = sinon.spy(PaymentService, 'start');
$controller('CashAcceptorController', { PaymentService: PaymentService });
chai.expect(mySpy.callCount).to.equal(1);
// another way of checking that it was called once
chai.assert(PaymentService.start.calledOnce);
});
});
This is my first time testing using Jasmine. I'm having trouble accessing the $scope variables in the spec. I have a failing test:
mysite ProductsDetailCtrl sets hey
Expected undefined to be 1.
Error: Expected undefined to be 1.
spec:
//= require helpers/load-angular-mysite-module
//= require products/controllers/products_detail_controller
describe('mysite', function() {
var $rootScope, $scope, $controller;
beforeEach(function() {
module('mysite');
});
describe('ProductsDetailCtrl', function() {
beforeEach(inject(function(_$rootScope_, _$controller_) {
$rootScope = _$rootScope_; // don't really
$scope = $rootScope.$new(); // understand what's
$controller = _$controller_; // going on in this function
controller = $controller('ProductsDetailCtrl', {
'$rootScope': $rootScope,
'$scope': $scope
});
}));
it('sets hey', function() {
expect($rootScope.hey).toBe(1);
});
});
});
controller:
app.controller('ProductsDetailCtrl', ['$scope', '$resource', function($scope, $resource) {
$scope.hey = 1;
....
Could someone explain to me how I would access the scope?
You just have to check for the property heyin your $scope not in the $rootScope:
describe('mysite', function() {
var scope, ProductsDetailCtrl;
beforeEach(function() {
module('mysite');
});
describe('ProductsDetailCtrl', function() {
beforeEach(inject(function($controller, $rootScope) {
// Create a mock scope for your controller.
scope = $rootScope.$new();
// Initialize the controller with the mocked scope.
ProductsDetailCtrl = $controller('ProductsDetailCtrl', {
$scope: scope
});
}));
it('sets hey', function() {
expect(scope.hey).toBe(1);
});
});
});
I'm getting this error when trying to test a controller in Karma:
Error: [$injector:unpr] http://errors.angularjs.org/1.2.14/$injector/unp
r?p0=%24elementProvider%20%3C-%20%24element
at c:/js/libs/angular/angular1.2.14/angular.min.js:32
at c (c:/js/libs/angular/angular1.2.14/angular.min.js:30)
at c:/js/libs/angular/angular1.2.14/angular.min.js:32
at c (c:/js/libs/angular/angular1.2.14/angular.min.js:30)
at d (c:/js/libs/angular/angular1.2.14/angular.min.js:30)
at c:/js/libs/angular/angular1.2.14/angular.min.js:31
at c:/js/libs/angular/angular1.2.14/angular.min.js:63
at c:/tests/unit/widget_tests/myTest.test.js:13
at d (c:/js/libs/angular/angular1.2.14/angular.min.js:30)
at workFn (c:/js/libs/angular/angular1.2.14/angular-mocks.js:2160)
I'm including all the angular files in karma.conf.js and compiling the controller like so:
var $scope, $http, $translate;
beforeEach(module('myApp.services'));
beforeEach(module('myApp.directives'));
beforeEach(inject(function ($rootScope, $controller, _$httpBackend_) {
$scope = $rootScope.$new();
$controller('myController', {$scope : $scope});
}));
describe('Initialization :', function(){
it('Should ', function() {
})
})
})
I needed to inject the $element into the controller, though compiling the full directive is another option.
$controller('myController', {$scope : $scope, $element :$('<div></div>')});
Posting this as the duplicate question is missing an example.
Your $scope variable is undefined so far.
By the way, the following should work:
var $scope, $httpBackend;
beforeEach(function() {
module('myApp', 'myApp.services', 'myApp.directives'));
inject(function ($rootScope, $controller, _$httpBackend_) {
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$controller('myController', {$scope : $scope});
}));
});
describe('Initialization :', function(){
it('Should ', function() {
})
});