I have developed an web application using Angular JS. I am getting few additional CR which needs to implemented in using TTD approach. We have return unit test cases using Jasmine and Karma. The challenge currently we face is when we try to write unit test case for multiple controllers. I have a main page return on Home Controller & its has an broadcast event in another controller. When i write a unit test case Object for the controller which has this broadcast event is not initialized.
Is there any way to inject the second controller as a dependent object. Answers with Reference Sample link or demo code is much appreciated.
You state you are using Jasmine and Karma so I assume you are unit testing. If you are "unit" testing you should test each controller individually while mocking, spy, all injected services.
beforeEach(inject(function ($rootScope, $controller) {
rootScope = $rootScope;
scope = $rootScope.$new();
controller = $controller('MyCtrl as ctrl', {
'$scope': scope
});
}));
it('', function(){
//Arrange
controller.counter = 0; // Your controller is listening on scope.$on to update this counter.
//Act
rootScope.$broadcast('xyz', {});
//Assert
expect(controller.counter == 1).toBe(true);
rootScope.$broadcast('xyz', {});
expect(controller.counter == 2).toBe(true);
rootScope.$broadcast('xyz', {});
expect(controller.counter == 3).toBe(true);
});
Just be careful with broadcast. Only a domain events (model updated/deleted/created), or something global (signin,signout) should travel over $broadcast. Otherwise, it should be replaced with a service + directive. An example is angular material https://material.angularjs.org/latest/api/service/$mdDialog that is 1 directive with a backing service that can be open/closed from anywhere.
You can inject any controller with the $controller service, e.g.
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller('MyCtrl', {
'$scope': scope
});
}));
see docs here:
https://docs.angularjs.org/api/ngMock/service/$controller
so what you do is inject that controller first, then your other controller. then the first controller will have been instantiated at the time the second gets instantiated.
I am new to angular, it seems to be possible to inject multiple controllers at once. However best practice is to generate a mock controller that behaves as you expect the second controller to behave. This reduces the number of things you are testing at once.
The following link may be helpful for creating a mock controller, http://www.powdertothepeople.tv/2014/08/28/Mocking-Controller-Instantiation-In-AngularJS-Unit-Test/ .
Related
I am building a dialog service. A dialog can have a controller, very similar to $mdDialog, like this:
myDialogService.show({
templateUrl: `<div ng-click="$ctrl.log()">Hello dialog</div>`,
controller: function() {
this.log = function() {
console.log("logged from myDialogController");
}
}
});
which works great. I invoke the controller that way:
locals.$scope = scope;
const invokeController = $controller(options.controller, locals, true);
const controller = invokeController();
if (options.controllerAs) {
scope[options.controllerAs] = controller;
} else {
const controllerAs = "$ctrl";
scope[controllerAs] = controller;
}
In angular-mock is the $componentController service, which can invoke component controllers. With my code, I only can invoke registered controllers, or given controller functions. This is not very helpful, as I only have components registered, not single controllers.
My question
Is it possible/recommended to use the $componentController in production? Or is there any AngularJS build in variant I have overseen?
$componentController belongs to ngMock module because it is useful for testing but is considered a hack in production. Since ngMock is big enough and isn't supposed to be available in production, it should be pasted in order to become available.
The proper way to solve this is to either have registered controllers that are reused as component controllers or import/export controller functions/classes with JS modules.
Since MdDialogController belongs to third-party module, isn't registered or exported and is small, it can be just pasted.
I have a service depending on $controller service. Inside of this service, $controller service takes a controller name and locals to instantiate this controller.
When I unit test this service, I would like to pass a dummy controller name, so I can test this service properly.
From reading angular.js source code, I know $controller service looks for registered controllers by controller name. Controllers are registered through register method of $controllerProvider. How can I access this method in unit test. I'm using Jasmine here for unit testing.
Any advices are appreciated.
If you are trying to test Angular's $controller service, and assuming you are using ngMocks, you can use $controller normally. In Angular's documentation, there's an example on how to use it.
Anyway, here's the sample from the docs:
describe('myDirectiveController', function() {
it('test case', inject(function($controller) {
// Your code goes here
});
});
beforeEach(function () {
module('myApp', function ($controllerProvider) {
$controllerProvider.register(dummyController, function () { });
});
});
I ended up with this. when my service call $controller(dummyController, controllerlocals), it was able to find this dummyController registered in its local variable controllers.
Hope this would help people have the same scenario
In Angular JS controllers, why do I have to inject both the scope and root scope, can't a controller have its scope as a child of the rootscope, and be injected by default, as in the view, I can always reference the attributes of both the scope and root scope as naked variables.......Why is this not for the controller too and even applied to the services as well?
Yes your thoughts are right about $rootScope. As per Angular Js documentation
Every application has a single root scope. All other scopes are descendant scopes of the root scope. Scopes provide separation between the model and the view, via a mechanism for watching the model for changes. They also provide an event emission/broadcast and subscription facility.
But reason of injecting is , thats how only Angular Works , injector functionality of Angular look for all injected dependency and create a reference of object. As per documentation also :
A root scope can be retrieved using the $rootScope key from the $injector.
Actually , More users confused why both are used in controller
$scope is used for communicate between controller and view. $scope binds a view (DOM element) to the viewmodel
But rootscope, There is only one rootscope in the app and it is shared among all the components of an app. $rootscope a global variable. all others $scopes are children of that $rootScope.
For Example
there are two controllers both have scope
var app = angular.module('myApp', []);
app.controller('Ctrl1', function ($scope, $rootScope) {
$scope.msg = 'World';
$rootScope.name = 'AngularJS';
});
app.controller('Ctrl2', function ($scope, $rootScope) {
$scope.msg = 'Dot Net Tricks';
$scope.myName = $rootScope.name;
});
rootscope only availble for all controllers but scope didn't get from another controller
Note
When you use ng-model with $rootScope objects then AngularJS updates
those objects under a specific $scope of a controller but not at
global level $rootScope. Create a private $scope for each controller
to bind it to the view.
I am attempting to trigger the $scope.$on() method in a controller that is fired when I $rootScope.broadcast() an event. I found this question useful: How can I test events in angular?, but I'm still having trouble detecting an event being broadcast from the $rootScope up through a controller's $scope.
So far I've managed to test that the $broadcast method is called on a corresponding $rootScope, but not that the $on method was called on a corresponding $scope when $broadcast is called on the $rootScope.
I attempted to $rootScope.$broadcast directly in my test, but my spy is not picking up on the event.
This is my controller:
angular.module('app.admin.controllers.notes', [])
.controller('NotesCtrl', function($scope) {
$scope.$on('resource-loaded', function(event, resource) { // I want to test this
$scope.parentType = resource.type;
$scope.parentId = resource.id;
});
});
This is my test:
describe('The notes controller', function() {
beforeEach(module('app.admin.controllers.notes'));
var scope, rootScope, NotesCtrl;
beforeEach(inject(function($controller, $injector, $rootScope) {
rootScope = $rootScope;
scope = $rootScope.$new(); // I've tried this with and without $new()
NotesCtrl = $controller('NotesCtrl', {$scope: scope}); // I've tried explicitly defining $rootScope here
}));
it('should respond to the `resource-loaded` event', function() {
spyOn(scope, '$on');
rootScope.$broadcast('resource-loaded'); // This is what I expect to trigger the `$on` method
expect(scope.$on).toHaveBeenCalled();
});
});
And here's the plunkr. I've included a passing test of the $broadcast method for reference, mainly because I setup the tests in the same manner.
I've read quite a few questions relating to testing events in AngularJS, and it always seems to be a scoping issue. I've heard that in Karma unit testing, $rootScope and $scope are the same thing, but I'm not really sure what the implication is. I've tried defining the $rootScope and the $scope as the same object, as well as explicitly injecting the $rootScope into the NotesCtrl during testing, but nothing makes my test go green.
How can I get the $on method in my NotesCtrl to fire for this test?
What makes it not working is the fact that you're spying the $on function. It works fine when not sying it: http://plnkr.co/edit/hNEj7MmDDKJcJ7b298OB?p=info. And the reason is actually simple. When an event is brodcasted, what is called is not the $on() function. What is called is the callback function passed as argument to $on() previously: the listener.
Note that, by spying the $on function, you're not testing your code here. All you're trying to test is that when broadcasting en event, child scopes receive it. So you're testing AngularJS itself.
Try to use
$rootScope.$emit('resource-loaded');
Works fine in my tests.
#kirill.buga
Using $broadcast is right, not $emit because :
https://docs.angularjs.org/api/ng/type/$rootScope.Scope
Dispatches an event name upwards through the scope hierarchy notifying the registered $rootScope.Scope listeners.
Problem of #ben-harold is trying to spy $on instead of the result of code in the $on.
I have been writing some Jasmine unit tests in Angular. In the first example I'm testing a controller.
myApp.controller('MyCtrl', function($scope, Config){
...
});
I have a configuration service (Config) that keeps configuration from the database and is injected into my controller. As this is a unit test, I want to mock out that configuration service altogether, rather than allowing execution to pass through it and using $httpBackend. Examples I found taught me about a $controller function I can use like this, in order to get an instance of my controller with my mocks injected in place of the usual collaborator:
beforeEach(inject(function($controller, $rootScope){
var scope = $rootScope.$new();
var configMock = {
theOnlyPropertyMyControllerNeeds: 'value'
};
ctrl = $controller('MyCtrl', {
$scope:scope,
Config: configMock
});
}));
But I also have other services that use the Config service. To help unit test them, I assumed there would be a similar $service function I could use to instantiate a service with whatever mocks I want to provide. There isn't. I tried $injector.get, but it doesn't seem to let me pass in my mocks. After searching for a while, the best I could come up with in order to instantiate a service in isolation (avoid instantiating its collaborators) is this:
beforeEach(function() {
mockConfig = {
thePropertyMyServiceUses: 'value'
};
module(function($provide) {
$provide.value('Config', mockConfig);
});
inject(function($injector) {
myService = $injector.get('MyService');
});
});
Is this the right way? It seems to be overriding the entire application's definition of the Config service, which seems maybe like overkill.
Is it the only way? Why is there no $service helper method?
For unit testing, it is common that you override a service for the sake of testing. However, you can use $provide to override an existing service instead of using inject, as long as you load the application before hand.
Assuming that you created Config using something like:
angular.moduel('...', [...]).factory('Config', function (...) {...});
If so, try this:
...
beforeEach(module("<Name of you App>"));
beforeEach(
module(function ($provide) {
$provide.factory('Config', function (...) {...});
});
);
...
After that, when you initialise your controller, it will get the mocked Config.