I'm trying to unit test $scope.$watch in controller, I don't know why $scope.$apply() in test code causes unexpected request error such as Error: Unexpected request: GET /locales/en.json. That's other part of the controller, why it's involved here?
However this error will not occur if I comment $scope.$apply, but of course $watch cannot be triggered in that case. Do I have to mock those requests like $httpBackend.whenGET('/locales/en.json').respond(''); ?
controller:
$scope.$watch(function(){
return $location.path();
}, function() {
$scope.currentPath = $location.path().match(/\/[a-z0-9A-Z_]*/)[0];
$scope.currentNav = 'menu.' + $scope.currentPath.replace('/', '');
});
jasmine:
describe('homeController', function() {
beforeEach(module('homeApp'));
var $rootScope, $scope, controller, $httpBackend, $location, $route, $window
beforeEach(inject(function($controller, _$rootScope_, _$httpBackend_, _$location_, _$route_, _$window_) {
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
controller = $controller('homeController', {$scope: $scope});
$httpBackend = _$httpBackend_;
$location = _$location_;
$route = _$route_;
$window = _$window_;
}));
describe('watch path', function() {
it('should change currentPath and currentNav', function() {
$location.path('/dashboard');
$scope.$apply();
$location.path('/images');
$scope.$apply();
expect($scope.currentPath).toBe('/images')
expect($scope.currentNav).toBe('menu.images')
})
})
})
update:
It's working after mocking all the http requests required. But still want to know why it affects those requests.
In above example you are not watching anything. $watch function will be called when value of object being watched changes. It will return you both old and new value for object.
$scope.someValue = 0;
$scope.$watch(
"$scope.someValue",
function handleFooChange(newValue, oldValue) {
console.log("$scope.someValue:", newValue);
}
);
Here someValue is being watched for change. Once value of someValue will change, callback function of $watch will be called.
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();
My controller method looks like this:
angular.module(_appName_)
.controller('myController', function ($scope, $rootScope) {
$rootScope.$broadcast('myObj', false);
......some code here.......
});
Jasmine test for testing call made to $rootScope.$broadcast looks like this:
describe("myController",function(){
var scope,rootScope;
beforeEach(angular.mock.inject(function($rootScope) {
scope = $rootScope.$new();
rootScope = $rootScope;
}));
describe('myController', function() {
it('rootScope broadcast called for myObj with false value', inject(function($controller, $rootScope) {
var requestObj = '{"key":"1234567890"}';
rootScope.requestObject = requestObj;
$controller('myController', {
$scope: scope,
$rootScope: rootScope
});
spyOn($rootScope, '$broadcast').and.callThrough();
expect($rootScope.$broadcast).toHaveBeenCalled();
}));
});
});
It always gives me the following error:
Expected spy $broadcast to have been called.
at Object.
When i try to put a breakpoint on the line where there is a call to broadcast in the controller method, it does hit the breakpoint while debugging. So the actual call is being made but the test doesn't recognize it somehow.
Can someone please let me know what am I missing here ?
I think you forgot to include your module in beforeEach function.
And then make sure you mock your spyOn($rootScope, '$broadcast') before you initialize your controller
$controller('myController', {
$scope: scope,
$rootScope: rootScope
});
Here is a plunker. :)
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.
I am new to unit testing and I am getting these errors even though I though my test was correct, I just cannot figure out what these errors mean and I have tried several things
Can't find variable: $rootScope
Error: Injector already created, can not register a module!
spec.js
describe('test broadcast', function () {
var $controller;
beforeEach(function() {
module('test');
inject(function (_$rootScope_, _$controller_) {
$rootScope = _$rootScope_;
spyOn($rootScope, '$broadcast');
// Notice how inject $controller here.
$controller = _$controller_;
});
});
it("should broadcast something", function ($rootScope) {
$controller('myCtrl', {
// Pass in the $rootScope dependency.
$rootScope: $rootScope.$new()
})
// Here we actually run the controller.
expect($rootScope.$broadcast).toHaveBeenCalledWith('update');
//someObj = { data: testData};
//expect($rootScope.$broadcast).toHaveBeenCalledWith('update', someObj);
});
})
controller
(function () {
var test= angular.module('test');
test.controller('myCtrl',
function($rootScope, $scope, $resource, $location, $route, $routeParams, $log, catalogData) {
$log.debug("myCtrl");
$log.debug(myCtrl);
$rootScope.$broadcast("update", {
data: testData
});
}); // catalogCtrl
})();
You have a variable called rootScope defined, not $rootScope - change your definition:
rootScope.$apply();
Though I personally like to define them like so:
var $rootScope;
beforeEach(inject(function(_$rootScope_) {
$rootScope = _$rootScope_;
}));
EDIT 2:
You cannot access $rootScope in your it function because it is not in the current javascript scope (not angular $scope, don't get confused).
You need to define it alongside your controller at the top.
var $controller, $rootScope
And remove $rootScope from your it function so you don't overwrite it.
// Notice there is no $rootScope parameter.
it("should broadcast something", function () {
//Code
}
You will also have to pass in your other dependencies.
After a discussion with the OP, the whole code should look like this:
describe('test broadcast', function () {
var $controller, $rootScope;
beforeEach(function() {
module('test');
inject(function (_$rootScope_, _ $controller_) {
$rootScope = _$rootScope_;
spyOn($rootScope, '$broadcast');
$controller = _$controller_;
});
});
it("should broadcast something", function () {
$controller('myCtrl', {
$scope: $rootScope.$new(),
catalogData: {}
})
expect($rootScope.$broadcast).toHaveBeenCalledWith('update', {catalog:{}})});
})
EDIT 1:
You are passing in the $scope dependency. $broadcast is called on the $rootScope so you need to pass that in. Like this:
var testScope = $rootScope.$new()
$controller('myCtrl', {
// Pass in the $rootScope dependency.
$rootScope: testScope
}
Original post (in case it's still useful to anyone)
You aren't actually calling your controller anywhere in your test suite.
You need to have something like
var $controller
beforeEach(inject(function (_$rootScope_, _$controller_) {
$rootScope = _$rootScope_;
spyOn($rootScope, '$broadcast');
// Notice how inject $controller here.
$controller = _$controller_;
}));
Then initialise it in your test:
it("should broadcast something", function () {
// Here we actually run the controller.
$controller('myCtrl', {
// Pass in the $rootScope dependency.
$rootScope: $rootScope.$new()
}
expect($rootScope.$broadcast).toHaveBeenCalledWith('catalogUpdate');
someObj = { catalog: catalogData};
expect($rootScope.$broadcast).toHaveBeenCalledWith('catalogUpdate', someObj);
});
This will remove the error about $rootScope.broadcast not being called.
Take a look at the "Testing Controllers" section here: https://docs.angularjs.org/guide/controller
As for not being able to register a module, this normally happens if you have an inject() before a beforeEach(module('abc')).
As the error says, you cannot register another module after inject has been called.
I have the following...
app.controller('testCtrl', function(testService){
testService.doSomething();
});
app.service('testService', function(){
this.doSomething = function(){...};
});
I want to use Jasmine to ensure doSomething is called once and only once. I seem to be having some trouble doing this.
Also, I am currently grabbing my controller from a compiled element like this...
var element = angular.element('<my-test-directive />');
controller = view.controller('testCtrl');
So extra appreciation if it fits with this sort of formatting
Update
I tried this...
describe("Testing", function () {
var $rootScope,
$scope,
$compile,
testService,
view,
$controller;
beforeEach(module("app"));
function createController() {
return $controller('testCtrl', {
$scope: scope,
testService:testService
});
}
function SetUpScope(_$controller_, _$compile_, _$rootScope_, _testService_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$controller = _$controller_;
testService = _testService_;
spyOn(testService, 'doSomething');
}
SetUpScope.$inject = ["$controller","$compile", "$rootScope", "testService"];
beforeEach(inject(SetUpScope));
it("On intitialization, the controller should register itself with the list service", function(done){
createController();
scope.$digest();
expect(workOrderService.doSomething).toHaveBeenCalled();
})
});
It seems to work
It is probably better to test controller in isolation and use Jasmine spies for this:
spyOn(testService, 'doSomething');
expect(testService.doSomething.calls.count()).toEqual(0);
Something like this should work in the actual test.
describe('testCtrl function', function() {
describe('testCtrl', function() {
var $scope, testService;
beforeEach(module('myApp'));
beforeEach(inject(function($rootScope, $controller, _testService_) {
$scope = $rootScope.$new();
testService = _testService_;
spyOn(testService, 'doSomething');
$controller('MyController', {$scope: $scope});
}));
it('should call testService.doSomething()', function() {
expect(testService.doSomething.calls.count()).toEqual(1);
});
});
});
Here is a quick plunkr http://plnkr.co/edit/Swso4Y
Depending on which version of Jasmine you are using you might need to use
expect(testService.doSomething.calls.length).toEqual(1);