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.
Related
Im using jasmine's spyOn function to try to determine if $scope.$broadcast have been called or not.
girlnames.spec.js -the controller
describe('Girl names controller', function() {
var vm,
$scope;
beforeEach(module('nameStats'));
beforeEach(inject(function($controller, $rootScope, $q, _$httpBackend_, _namesService_) {
vm = $controller('girlNames', {
$scope: $rootScope.$new()
});
$scope = $rootScope.$new()
}));
it('addPersonManually should trigger $scope.$broadcast', function() {
spyOn($scope, '$broadcast').and.callThrough()
vm.addPersonManually(p)
$scope.$digest();
expect($scope.$broadcast).toHaveBeenCalled()
});
});
girlnames.js - the controller
"use strict";
angular.module('nameStats').controller('girlNames', girlNames);
girlNames.$inject = ['$scope', 'namesService'];
function girlNames($scope, namesService) {
var vm = this;
vm.addPersonManually = addPersonManually;
function addPersonManually(person) {
$scope.$broadcast('personSelected', person);
}
}
The output in the console:
Expected spy $broadcast to have been called.
Take a closer look at the way you are instantiating your controller
beforeEach(inject(function($controller, $rootScope, $q, _$httpBackend_, _namesService_) {
vm = $controller('girlNames', {
$scope: $rootScope.$new()
});
$scope = $rootScope.$new();
}));
You inject one scope instance and use a totally different one for testing.
Your code should look like this
beforeEach(inject(function($controller, $rootScope) {
$scope = $rootScope.$new();
vm = $controller('girlNames', {
$scope: $scope
});
}));
Advanced tip
Consider getting rid of local variables in your tests. Karma keeps references to all the test suits until all of them finish running thus causing a huge memory consumption. It can even cause process to fail if you have enough tests (it was a couple thousands in our case). Useful article.
Use this instead
beforeEach(inject(function($controller, $rootScope) {
this.$scope = $rootScope.$new();
this.ctrl = $controller('girlNames', {
$scope: $scope
});
}));
it('addPersonManually should trigger $scope.$broadcast', function() {
spyOn(this.$scope, '$broadcast').and.callThrough()
this.ctrl.addPersonManually(p)
this.$scope.$digest();
expect(this.$scope.$broadcast).toHaveBeenCalled()
});
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 working to initiate a controller sits inside a directive. I have some tests I need to run but right now I am not able to access the controller with ng-Mock.
describe('hero Directive', function () {
var $compile,
$rootScope,
$scope,
element,
ctrl;
beforeEach(function () {
angular.mock.module('ha.module.core');
angular.mock.inject(function (_$compile_, _$rootScope_, _$controller_, $templateCache) {
$compile = _$compile_;
element = angular.element("<div exlore-hereo></div");
$compile(element)($rootScope);
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
ctrl = _$controller_('ExploreHeroController', { $scope: $scope });
console.log(ctrl)
$scope.$digest();
});
});
afterEach(function () {
// need to remove the element element.remove();
});
describe('directive controller', function () {
it('should dispatch call $emit with $methodsBound', function () {
//spyOn($scope, '$emit');
spyOn($scope, 'ControllerName');
//expect(scope.$emit).toHaveBeenCalledWith('$methodsBound');
//expect(ctrl).toHaveBeenCalled();
});
});
});
I created an element compiled it and called the $digest method.
The error that I got was
Argument 'scope' is required.
So I tried spying on the it with jasmine
spyON($scope, 'ControllerName');
My controller inside of my directive is pretty basic.
var ControllerName = function($scope) {
$scope.$emit('$method');
}
It seems like I need a spy, but I am not sure why the one I created does not work.
You can try to spy on $scope, but note that ControllerName is not a member of the $scope object.
However, $emit is....
The thing is, that you call $emit in the controllers constructos, therefore you have to spy on it before:
beforeEach(function () {
...
$scope = $rootScope.$new();
spyOn($scope, '$emit');
ctrl = _$controller_('ExploreHeroController', { $scope: $scope });
...
});
describe('directive controller', function () {
it('should dispatch call $emit with $methodsBound', function () {
expect($scope.$emit).toHaveBeenCalledWith('$methodsBound');
});
});
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.
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);