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. :)
Related
I'm testing a directive ('planListing') that has a dependency on a service called 'planListingService'. This service has a dependency to another service called 'ajax' (don't shoot the messenger for the bad names).
I'm able to compile the directive, load its scope and get the controller WITH A CAVEAT. As of now I am being forced to mock both services 'planListingService' and 'ajax' otherwise I will get an error like this:
Error: [$injector:unpr] Unknown provider: ajaxProvider <- ajax <- planListingService
http://errors.angularjs.org/1.3.20/$injector/unpr?p0=ajaxProvider%20%3C-%20ajax%20%3C-%20planListingService
I thought that because I was mocking up the 'planListingService' that I wouldn't have to actually bother with any implementation nor any dependencies of this service. Am I expecting too much?
Here is the code in a nutshell:
planListing.js
angular.module('myApp')
.directive('planListing', planListing)
.controller('planListingCtrl', PlanListingCtrl);
function planListing() {
var varDirective = {
restrict: 'E',
controller: PlanListingCtrl,
controllerAs: 'vm',
templateUrl: "scripts/directives/planListing/planListing.html";
}
};
return varDirective;
}
PlanListingCtrl.$inject = ['planListingService'];
function PlanListingCtrl(planListingService) {
...
}
planListingService.js
angular.module('myApp')
.factory('planListingService', planListingService);
planListingService.$inject = ['$q', 'ajax'];
function planListingService($q, ajax) {
...
}
ajax.js
angular.module('myApp')
.factory('ajax', ['backend', '$browser', 'settings', '$http', '$log',
function (backend, $browser, settings, $http, $log) {
...
planListing.spec.js
describe('testing planListing.js',function(){
var el,ctrl,scope,vm;
var service;
module('myApp');
module('my.templates');
beforeEach(module(function ($provide){
// This seems to have no effect at all, why?
$provide.service('planListingService', function () {
this.getAllPricePlans=function(){};
});
// I don't get the error if I uncomment this:
// $provide.service('ajax', function ($q) {
// this.getAllPricePlans=function(){};
// });
}));
beforeEach(function() {
module('myApp');
module('my.templates');
});
beforeEach(angular.mock.inject(function (_$compile_,_$rootScope_,_$controller_){
$compile=_$compile_;
$rootScope = _$rootScope_;
$controller = _$controller_;
el = angular.element('<plan-listing></plan-listing>');
scope = $rootScope.$new();
$compile(el)(scope);
scope.$digest();
ctrl = el.controller('planListing');
scope = el.isolateScope() || el.scope();
vm = scope.vm;
}));
describe('testing compilation / linking', function (){
it('should have found directive and compiled template', function () {
expect(el).toBeDefined();
expect(el.html()).not.toEqual('');
expect(el.html()).toContain("plan-listing-section");
});
});
it('should have a defined controller',function(){
expect(ctrl).toBeDefined();
});
it('should have a defined scope',function(){
expect(ctrl).toBeDefined();
});
});
So why is that I need to mock up the 'ajax' service even though I am mocking up 'planListingService' which is the one calling the 'ajax' service?
Thanks!
I have been there... feels like bad start But i think your directive is depend on the service and you need to inject it in order to directive can work with this, Just by calling directive it doesn't mean that it's going to inject it in your test. It will look for it and if it's not injected it will give you error
you could do so before testing your directive
beforeEach(inject(function ($injector) {
yourService = $injector.get('yourService');
})
For documentation purposes, here is the answer (thanks #estus for noticing this):
Indeed the problem was related to the incorrect initialization of my modules. Instead of this:
describe('testing planListing.js',function(){
var el,ctrl,scope,vm;
var service;
module('myApp');
module('my.templates');
...
I should've done this:
describe('testing planListing.js',function(){
var el,ctrl,scope,vm;
var service;
beforeEach(module('myApp'));
beforeEach(module('my.templates'));
...
After that things started working again as expected.
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'm having a hard time to get my head around Jasmine. The following test is failing with the message "Expected spy init to have been called. at Object.."
The Test
beforeEach(module('myModule'));
it('Should execute myCtrl.init() on controller instantiation', function () {
var $scope = $rootScope.$new();
$scope.foo = 'bar';
var MyCtrl = $controller('MyCtrl', {
$scope: $scope
});
spyOn($scope, 'init');
expect($scope.init).toHaveBeenCalled();
expect($scope.foo).toBe('bar');
});
The Controller
angular.module('myModule')
.controller('MyCtrl', [
'$scope'
function($scope) {
$scope.init = $scope.init || function init () {
$scope.foo = $scope.foo || 'baz';
};
$scope.init();
}]);
What am I missing?
Basically, you can't setup the spy after the controller is created, or it won't catch the method being called at controller invocation. But that is not something that really needs to be tested because you can tell based on the controller code that it is getting called when the controller is invoked, and the only thing that would keep it from being called is some kind of syntax error or something.
You are ok to test whether some $scope property like $scope.foo is set to the correct value, though.
This seems like a similar problem to AngularJs unit test - Check if "Init" function was called
Edit:
If you really needed to though, you could use a scope mock like this (This is what you should do to mock other dependencies you might have to your controller) and inject it instead of the $rootScope way, but then you wouldn't have $scope.foo getting set because $scope.init would not be initially undefined:
var scopeMock;
beforeEach(module('myModule'));
it('Should execute myCtrl.init() on controller instantiation', function () {
scopeMock = jasmine.createSpyObj('$scope', ['init']);
var MyCtrl = $controller('MyCtrl', {
$scope: scopeMock
});
//This will be true according to the controller you defined
expect(scopeMock.init).toHaveBeenCalled();
});
I'm getting the following error in the js console log when attempting to assign the controller to 'myCtrl' variable in a jasmine test: 'myApp.controller is not a function'
In the controller itself, the controller is defined as the following and this is what's triggering the error I mentioned:
myApp.controller('myCtrl', ...
And this is how I'm trying to access the controller in my spec file:
beforeEach(function() {
module('myApp');
});
it('tests the controller', inject(function($controller) {
var myCtrl = $controller('myCtrl');
}));
Any idea why it's still throwing this error? Seems to be a missing dependency but not sure where..
If you want to test your controller , following is one way of writing a test case
describe('YourControllerName', function () {
var $rootScope, scope, $controller ;
beforeEach(angular.mock.module('YourModuleName'));
beforeEach(inject(function ($rootScope, $controller ) {
scope = $rootScope.$new();
$controller('YourControllerName', {
$scope: scope
});
}));
it('Should do this', function () {
//assertions
});
});
Thanks - It turns out it was something as simple as I need to list myApp.js before myController.js in the SecRunner.html. I was pretty sure I tried that previously.. but there you go.
I've been trying to get started with unit testing in angular with karma and jasmine, and i've been pulling my hair out trying to wrap my head around how to test controllers with dependencies. I tried mocking a spy with a jasmine spyObj and registering it in the beforeEach hook, but for some reason the spy isn't being recognized.
Here's the code:
angular.module('testModule', [])
.controller('TestController', [
'$scope',
'TestService',
function ($scope, TestService) {
$scope.data = TestService.load();
}])
.factory('TestService', function () {
return {
load: function(){
return "foo";
}
}
});
and here's the test
describe('TestController', function() {
var $controller, $scope, TestService;
beforeEach(module('testModule'), function($provide){
TestService = jasmine.createSpyObj("TestService", ["load"]);
TestService.load.andReturn("bar");
$provide.value("TestService", TestService)
});
beforeEach(inject(function(_$controller_, $rootScope, _TestService_) {
$scope = $rootScope.$new();
TestService = _TestService_;
$controller = _$controller_('TestController', {
$scope: $scope,
TestService: TestService
});
}));
it('should set $scope.data to bar when TestService.load is called', function() {
expect(TestService.load).toHaveBeenCalled();
expect($scope.data).toEqual("bar");
}); });
Both assertions in the test fail.
I get 'Error: Expected a spy, but got Function' when i call expect(TestService.load).toHaveBeenCalled();
and if I call expect($scope.data).toEqual("bar"), I get Expected 'foo' to equal 'bar'. "Foo" is coming from the actual service, not the spy object.
Thanks for your help.
Instead of jasmine.createSpyObj, it will be easier to use the existing service that the $injector provides and then just mock the single method. You can achieve this with spyOn instead:
describe('TestController', function() {
var $controller, $scope, TestService;
beforeEach(module('testModule'));
beforeEach(inject(function(_$controller_, $rootScope, _TestService_) {
$scope = $rootScope.$new();
TestService = _TestService_;
spyOn(TestService, 'load').and.returnValue('bar');
$controller = _$controller_('TestController', {
$scope: $scope,
TestService: TestService
});
}));
it('should set $scope.data to bar when TestService.load is called', function() {
expect(TestService.load).toHaveBeenCalled();
expect($scope.data).toEqual("bar");
});
});
In your beforeEach you are injecting in _TestService_ and then overwriting the one you declared in the previous beforeEach via:
TestService = _TestService_;
Remove that code and your test should succeed.
Also there is no need to do this:
$provide.value("TestService", TestService)
Basically you're trying to use Angular's dependency injection when you're manually injecting things which is unnecessary.