Testing angular controllers that have many dependencies - angularjs

I am trying to test a controller.
someModule.controller('MyController', function($rootScope, $scope, dep1, dep2) {
...
$scope.aMethod = function() {
...
}
function bMethod() {
...
}
...
}]);
And I define my test this way:
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
console.log($controller);
MyController = $controller('MyController', {
$rootScope : $rootScope,
$scope: scope
});
console.log('Some debug message');
console.log(MyController);
}));
And I get this output:
LOG: function (expression, locals, later, ident) { ... }
LOG: 'Some debug message'
LOG: {}
Even if I put in dep1 and dep2 I get the same results. So scope and rootScope have to be present otherwise I get an error when it runs.
I am not certain why this isn't working, as the last output is empty, so there are no functions in the controller, which is wrong.
I want to test bMethod as a minimum, but it appears to not be creating my controller correctly.

The bMethod it's not attached to scope and neither to thisvariable, therefore it's not available in the tests. There are two ways to add the method in the test:
$scope.bMethod = bMethod; //Attached to scope
this.bMethod = bMethod; //Attached to controller
In your describe block, you prints MyController. This it's a instance of controller. If you want get the a method provided in the example, prints scope and you'll see the a method.
check this codepen --> http://codepen.io/gpincheiraa/pen/WwXGxV

There are no properties on controller instance, because they weren't defined.
It is scope object that has got aMethod property, not MyController:
expect(scope.aMethod).toBe(jasmine.any(Function));

Related

Jasmine Js - SpyOn Fakecall during the controller initialization

I have seen a set of duplicates for this question but was unable to solve the issue.
I have a controller and during the controller initialization, fetchtemplate() is getting called first and then my mock fetchtemplate() is getting called.
How do I stop the actual(controller) fetchtemplate() getting called during the controller initialization? My intention is to mock the function fetchtemplate() in my spec.Please have a look at my spec -
describe("...",function(){
beforeEach(inject(function($controller,...) {
scope = $rootScope.$new();
this.init = function() {
$controller('ChangeControlCreateController', {
$scope: scope
});
}
}));
describe('Function', function() {
it("-- check for trueness",function(){
this.init() ; //Initialization of the controller
spyOn(scope,'fetchtemplate').and.callFake(function() {
return 101;
});
var fakeResponse = scope.fetchtemplate();
expect(scope.fetchtemplate).toHaveBeenCalled();
expect(fakeResponse).toEqual(101);
});
});
});
I have tried placing the spyOn before the this.init() which gave error as the fetchtemplate() doesn't exist at that time to spyOn.
My controller code structure looks like -
angular.module('...', [...])
.controller('ChangeControlCreateController', ["$scope"...,
function ChangeControlCreateController($scope,...) {
$scope.fetchtemplate = function() {
console.log("controller's function");
...
};
$scope.fetchtemplate();
});
The result what I am getting is - First the console item "controller's function" and then the spec is executing with mock function. I want the mock function to execute without the controller's function to execute
So if I understand correctly you are doing some call to a function that is doing something you want to prevent for test purposes. Probably an http call or some thing of the sort ?
Whatever it is doing the proper way to handle something like that is usually to put that method inside a service instead and then to spy on that service method. Here is an example of test if the service is TemplateService :
describe("...",function(){
var $controller, scope, TemplateService, YourController;
beforeEach(inject(function(_$controller_, _TemplateService_, ...) {
scope = $rootScope.$new();
$controller = _$controller_;
TemplateService = _TemplateService_;
}
it("-- check for trueness",function(){
spyOn(TemplateService,'fetchTemplate').and.returnValue('101');
YourController = $controller('YourController');
expect(...);
});
});
I hope that's helpful

Angular test - triggering event from root scope with $emit

I am trying to test a controller which is calling a service. When this service has finished its job it is dispatching an event off the root scope with $emit. I am trying to simulate this in my tests, but I am unable to get the event handler to run. Can anyone see what I am doing wrong?
This is a basic version of my tests:
Files = Mocks.Files;
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
$controller("HelpController", { $scope: scope }); // inherit scope from parent
rootScope = $rootScope.$new();
rootScope.profile = {
....
};
$controller("HelpContentEditorController", {
$scope: scope,
$rootScope: rootScope
Files: Files
});
scope.$apply();
}));
it("should save FileResource to API", function() {
spyOn(Files, "saveFileAPI");
scope.saveContent();
scope.$apply();
rootScope.$emit("allFilesUploaded", []);
rootScope.$apply();
expect(Files.saveFileAPI).toHaveBeenCalled();
});
And then inside the controller I am testing
function saveContent() {
var blob = new $window.Blob([JSON.stringify($scope.content)], {type: "text/json"});
blob.name = "help.json";
$rootScope.$on("allFilesUploaded", onFilesUploaded);
Files.upload([blob]);
}
function onFilesUploaded(event, files) {
Files.saveFileAPI(files[0], files[0].extensions[0]).then(function(data) {
...
}, function (error) {
...
});
}
The test runs without errors, but the onFilesUploaded method is never called, so it fails. I am not sure if it something to do with the way I am inheriting scope, etc but I have tried a few alternatives
Thanks!
May be it helps to clarify what your tests are doing:
In your beforeEach section you create scope = $rootScope.$new() and rootScope = $rootScope.$new(). This way, scope and rootScope are siblings. Both are children of $rootScope and there is no parent-child relationship between them as the names suggest.(If you want rootScope to be the actual and only rootScope than just use rootScope = $rootScope).
Than look at your $emit: You provide an empty Array [] as additional arguments but in your event handler you expect at least one element files[0] and use it as argument in your Files.saveFileApi(files[0] ...)
As far as I can see, your eventHandler will be called but crashes.

Why is there a difference between these approaches in testing a controller

While doing a Jasmine test for an Angular Controller, I find a difference between these two approaches. There shouldn't be, but there is. That said, using debug, I find in both cases the correct mocked items are coming thru, however the tests behave differently.
First: Here we mock service items which are then injected using DI into the controller at creation.
$provide.value('core.data.CompanyService', companyService);
$provide.value('core.list.ListGenerator', listGeneratorFactory);
$provide.value('core.actions.ActionContext', actionContext);
ActivitiesCtrl = $controller('activities.ActivitiesCtrl', {
$scope: scope
});
Second:
Here we explicitly specify the injected service items in the controller creation:
ActivitiesCtrl = $controller('activities.ActivitiesCtrl', {
$scope: scope,
'core.lists.ListGenerator': listGeneratorFactory,
'core.actions.ActionContext': actionContext,
'core.data.CompanyService': companyService
});
If your first snippet of code is actually what you have, then I think I see the problem; providers should be set in a module config section and $controller should be accessed within an inject callback.
Given proper setup of the mocks prior to this, the following are equivalent
Providers on the $injector
beforeEach(function() {
module('your.controller.module', function($provide) {
$provide.value('core.list.ListGenerator', listGeneratorFactory);
$provide.value('core.actions.ActionContext', actionContext);
$provide.value('core.data.CompanyService', companyService);
});
inject(function($controller) {
// assuming scope is defined somewhere
ActivitiesCtrl = $controller('activities.ActivitiesCtrl', {
$scope: scope
});
});
});
Controller locals
beforeEach(inject($controller) {
// again, assuming scope is defined somewhere
ActivitiesCtrl = $controller('activities.ActivitiesCtrl', {
$scope: scope,
'core.lists.ListGenerator': listGeneratorFactory,
'core.actions.ActionContext': actionContext,
'core.data.CompanyService': companyService
});
}));

How to test bottomSheet-controller containing locals in jasmine?

I am using the angular-material bottomSheet using the locals to pass an object (see here: https://material.angularjs.org/#/api/material.components.bottomSheet/service/$mdBottomSheet ).
Now I am confused about how you would correctly test them in jasmine/karma.
Below is the usual way of injecting; the way I tried to inject my local (myLocal).
beforeEach(inject(function ($controller, $rootScope, _myLocal_) {
scope = $rootScope.$new();
myLocal = _myLocal_;
UserActionsCtrl = $controller('UserActionsCtrl', {
$scope: scope
});
}));
That gives me the following error:
Error: [$injector:unpr] Unknown provider: myLocalProvider <- myLocal
Of course there is nothing to be found, since I didn't call the bottomSheet with the filled locals. But all attempts to mock them failed. If anyone knows, how the locals can be mocked, I would greatly appreciate it!
You are very close sir!
I take it your controller declaration looks something like this:
.controller('UserActionsCtrl', function ($scope, $mdBottomSheet, myLocal)
I know that the Angular Material provides it through their 'locals' argument in the options parameter as it says here, but you just assume that it's the same as any other controller sharing data with another controller where you pull it in as a provider to the receiving controller, so your tests just reflect it!
Try this instead, you must provide the value into the controller instance as so:
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
var myLocal = {};
UserActionsCtrl = $controller('UserActionsCtrl', {
$scope: scope,
myLocal: myLocal
});
}));

Spy on scope function that executes when an angular controller is initialized

I want to test that the following function is in fact called upon the initialization of this controller using jasmine. It seems like using a spy is the way to go, It just isn't working as I'd expect when I put the expectation for it to have been called in an 'it' block. I'm wondering if there is a special way to check if something was called when it wasn't called within a scope function, but just in the controller itself.
App.controller('aCtrl', [ '$scope', function($scope){
$scope.loadResponses = function(){
//do something
}
$scope.loadResponses();
}]);
//spec file
describe('test spec', function(){
beforeEach(
//rootscope assigned to scope, scope injected into controller, controller instantiation.. the expected stuff
spyOn(scope, 'loadResponses');
);
it('should ensure that scope.loadResponses was called upon instantiation of the controller', function(){
expect(scope.loadResponses).toHaveBeenCalled();
});
});
You need to initialise the controller yourself with the scope you've created. The problem is, that you need to restructure your code. You can't spy on a non-existing function, but you need to spyOn before the function gets called.
$scope.loadResponses = function(){
//do something
}
// <-- You would need your spy attached here
$scope.loadResponses();
Since you cannot do that, you need to make the $scope.loadResponses() call elsewhere.
The code that would successfully spy on a scoped function is this:
var scope;
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
$controller('aCtrl', {$scope: scope});
scope.$digest();
}));
it("should have been called", function() {
spyOn(scope, "loadResponses");
scope.doTheStuffThatMakedLoadResponsesCalled();
expect(scope.loadResponses).toHaveBeenCalled();
});
Setting the spy before controller instantiation (in the beforeEach) is the way to test controller functions that execute upon instantiation.
EDIT: There is more to it. As a comment points out, the function doesn't exist at the time of ctrl instantiation. To spy on that call you need to assign an arbitrary function to the variable (in this case you assign scope.getResponses to an empty function) in your setup block AFTER you have scope, but BEFORE you instantiate the controller. Then you need to write the spy (again in your setup block and BEFORE ctrl instantiation), and finally you can instantiate the controller and expect a call to have been made to that function. Sorry for the crappy answer initially
The only way I have found to test this type of scenarios is moving the method to be tested to a separate dependency, then inject it in the controller, and provide a fake in the tests instead.
Here is a very basic working example:
angular.module('test', [])
.factory('loadResponses', function() {
return function() {
//do something
}
})
.controller('aCtrl', ['$scope', 'loadResponses', function($scope, loadResponses) {
$scope.loadResponses = loadResponses;
$scope.loadResponses();
}]);
describe('test spec', function(){
var scope;
var loadResponsesInvoked = false;
var fakeLoadResponses = function () {
loadResponsesInvoked = true;
}
beforeEach(function () {
module('test', function($provide) {
$provide.value('loadResponses', fakeLoadResponses)
});
inject(function($controller, $rootScope) {
scope = $rootScope.$new();
$controller('aCtrl', { $scope: scope });
});
});
it('should ensure that scope.loadResponses was called upon instantiation of the controller', function () {
expect(loadResponsesInvoked).toBeTruthy();
});
});
For real world code you will probably need extra work (for example, you may not always want to fake the loadResponses method), but you get the idea.
Also, here is a nice article that explains how to create fake dependencies that actually use Jasmine spies: Mocking Dependencies in AngularJS Tests
EDIT: Here is an alternative way, that uses $provide.delegate and does not replace the original method:
describe('test spec', function(){
var scope, loadResponses;
var loadResponsesInvoked = false;
beforeEach(function () {
var loadResponsesDecorator = function ($delegate) {
loadResponsesInvoked = true;
return $delegate;
}
module('test', function($provide) {
$provide.decorator('loadResponses', loadResponsesDecorator);
});
inject(function($controller, $rootScope) {
scope = $rootScope.$new();
$controller('aCtrl', { $scope: scope });
});
});
it('should ensure that scope.loadResponses was called upon instantiation of the controller', function () {
expect(loadResponsesInvoked).toBeTruthy();
});
});
I didn't quite understand any of the answers above.
the method I often use - don't test it, instead test the output it makes..
you have not specified what loadResponses actually does.. but lets say it puts something on scope - so test existence of that..
BTW - I myself asked a similar question but on an isolated scope
angular - how to test directive with isolatedScope load?
if you still want to spy - on an unisolated scope, you could definitely use a technique..
for example, change your code to be
if ( !$scope.loadResponses ){
$scope.loadResponses = function(){}
}
$scope.loadResponses();
This way you will be able to define the spy before initializing the controller.
Another way, is like PSL suggested in the comments - move loadResponses to a service, spy on that and check it has been called.
However, as mentioned, this won't work on an isolated scope.. and so the method of testing the output of it is the only one I really recommend as it answers both scenarios.

Resources