I'm using Jasmine to unit test my Angular app. It's pretty easy to test if a method of a function has been called using something like:
spyOn($rootScope, "$emit");
expect($rootScope.$emit).toHaveBeenCalled();
But I can't find a way to check when a function has been called (without a method), for e.g. I'm using $anchorScroll(); in one controller and I have no idea where to apply the above code to this guy. I've seen some Jasmine examples where they were using expect(window.myFunction()).toHaveBeenCalled(), but this doesn't work with Angular's DI.
I can't try it myself at the minute but maybe you could just inject a mock $anchorScroll instead?
var $anchorScroll = jasmine.createSpy('anchorScroll');
$controller('MyCtrl', {
$anchorScroll: $anchorScroll
});
expect($anchorScroll).toHaveBeenCalled();
This should just create a blank spy, one which will take any arguments and do nothing but keep track of the calls for test usage.
Related
using angular 1.5 / es6, I have the following code that I want to test :
this.$onDestroy = function () {
this.$interval.cancel(someIntervalTimer);
};
I cant find out how to trigger the destruction of the controller in jasmine unit test in order to test what is happening there.
I tried to $destroy() $scope and $rootScope, also $broadcast('$destroy'), etc but none of these seems to trigger the destruction.
$onDestroy hook is called by directive/component compiler, as well as other controller hooks.
When controller is tested directly with $controller or $componentController, hook method is supposed to be called manually:
spyOn(controllerInstance.$interval, 'cancel');
expect(controllerInstance.$onDestroy).toBe(jasmine.any(Function));
controllerInstance.$onDestroy();
expect(controllerInstance.$interval.cancel).toBeCalledWith(jasmine.any(Object));
beforeEach(inject(function (_$controller_, _$injector_) {
ctrl = _$controller_(...) // 1
ctrl = _$injector_.get('$controller')(...) // 2
}));
What are the differences and which way is preferred?
Declaring _$controller_ in the parameters of the function passed to inject() makes the framework inject the $controller service in the function.
Calling $injector.get('$controller') explicitly asks the $controller service to the framework.
So it's basically the old "dependency injection vs. factory" debate. Should the framework provide the dependency to the test, or should the test ask it dependency to the framework?
The first one is definitely preferrable in production code: it makes your code testable, and is the way you're supposed to use the framework.
In tests, there is no significant difference, although I prefer the first one too.
There are no differences in an ES5 context. The first one is the suggested way by Angular, as it is used by them and also in their docs and tutorials.
When you pass a function to inject, angular stringifies it and parses fuction parameters to figure out what providers to inject. Then it calls the function with correct providers. For example Angular probably calls $inject.get to get the instance of $controller.
It makes a difference for ES6 because when you stringify an arrow function, it doesn't start with "function(..." so Angular cannot parse it before Angular 1.5. In that case you need to use array notation: inject(['$controller', $controller => { ... }]).
I'm trying to compose some unit tests in Karma/Jasmine for a particular module in my project, destination-filters.
Module Decleration:
angular.module('destination-filter', ['ngSanitize']);
My tests fail unless I remove ngSanitize as a dependency. To my understanding that is because when the module is instantiated it will try and pull in that dependency but because in my spec.js file I haven't declared that module it is failing.
Spec File:
describe('Destination Filter Controller', function () {
// Set the variables
var $controller;
var mockNgSanitize;
beforeEach(module('destination-filter'));
beforeEach(function() {
module(function($provide) {
$provide.value('ngSanitize', mockNgSanitize);
});
});
beforeEach(inject(function (_$controller_) {
$controller = _$controller_('DestinationFilterController');
}));
it('should expect the controller to not be null', function() {
// Check the controller is set
expect($controller).not.toBeNull();
});
});
Previously, when mocking out services or functions, the $provide method has proven very useful but I'm not sure my use of it is correct here. I'm assuming $provide used in this way can't mock entire modules but rather services?
To clarify, if I remove the ...['ngSantize'])... from my module deceleration the tests instantiate correctly. The error I am receiving with it left in is Error: [$injector:modulerr] destination-filter
There are three options you could take for using ngSanitize in your tests:
inject the service into your test
stub a method call on ngSanitize
mock the entire ngSanitize service
The option you choose is really dependent on the use of ngSanitize in your working code (not your test code).
Whichever one you go for you need to make the service available in your test, there is no need for $provider (this covers option 1 and there is no need to do any more than this if you just want to make this available to your filter):
beforeEach(module('ngSanitize'));
beforeEach(inject(function(_ngSanitize_) { // the underscores are needed
mockNgSanitize = _ngSanitize_;
}));
Also, make sure that all js files are picked up and loaded by karma. You can define this in karma.conf.js by adding them to the files: property.
2. Stub a method on the service
I like stubs and find them very useful when writing tests. Your tests should only test one thing, in your case a filter. Stubs give you more control over your tests and allow you to isolate the thing under test.
Typically filters, controllers, anything call on lots of other things (services or factories like $http or ngSanitize).
Assuming that your filter is using ngSanitize's $sanitize to sanitize some html you could stub out that method to return sanitized html you have defined to test against your expectations:
// in a beforeEach
spyOn(mockNgSanitize, "$sanitize").and.returnValue('<some>sanitized<html>');
mockNgSanitized.$sanitize(yourDirtyHtml);
See the jasmine docs for more info.
You might have to play around with spying on the right service but this should work ok.
3. Mock the entire service
I don't think you want to go with this option because it will drive you insane figuring out what needs mocking plus mocks can create unrealistic expectations and also, not particularly useful for your use case. If you really want to have a go then something like the below is heading in the right direction (again see the jasmine docs)
beforeEach(function() {
mockNgSanitize = ('ngSanitize', ['linky', '$sanitize', '$sanitizeProvider'];
});
it('mocks the ngSanitize service', function() {
expect(mockNgSanitize.linky).toBeDefined();
});
NB: in all the code above make sure you continue to declare any variables up at the top of your describe block.
I have a function like this inside a controller
$scope.closeMenu = function () {
$('#menu').hide();
};
If a functions returns a value I am able to test using
expect($scope.value).toEqual();
How do we test the above function using jasmine. Please help thanks
If a functions returns a value I am able to test using expect($scope.value).toEqual(); How do we test the above function using jasmine
You should rewrite your function, so as it only set a variable of the model. Then it will be testable using the way you already know in Jasmine. One of the point of angular is that you don't manipulate the DOM from the controller. Providing you follow these guidelines, controllers are far easier to test.
Moreover, in your case rewriting is very straightforward:
$scope.closeMenu = function () {
$scope.isOpen = false;
};
Template:
... id="menu" ng-show="isOpen" ...
In case you still need to test some characteristics of a DOM element, for example it's visibility, jasmine-jquery might be useful for you. Example:
expect($('#menu')).toBeHidden()
I would like to inject this function into the ng-init directive when I instantiate a controller in an angular test, in the html I would set it up like this:
<div ng-controller="my-ctrl" ng-init="initialize({updateUrl: 'list_json'})
In my test I could just write a function like this which would stick the correct stuff in the scope but I think it is messy. Also I am worried about the function currently hard-wired in the html messing it up in my midway test
function initController($scope){
$scope['updateUrl'] = 'list_json'
}
I don't think that ng-init creates any functions by itself, so I think it's just a wrong way of using it (judged by your snippets).
What I usually do is create the init function myself and then test it without a problem in the controller's unit test.
<div ng-controller="my-ctrl" ng-init="init({ updateUrl : 'list_json' })">...</div>
function MyController($scope){
$scope.init = function(config) {
$scope.updateUrl = config.updateUrl;
}
}
Then in Jasmine you can without a problem write a test which would call the init() function (boilerplate skipped):
// Given
var config = { updateUrl : 'list_json' };
// When
myController.init(config);
// Then
// ...
Important comment about Unit Testing
If you think that you should be testing if your HTML markup is calling init() with given parameters, then I think you are wrong. There is simply nothing to test. There is no logic there at all! Your tests should be verifying that components work how they should work, if programmer will make a mistake with passing wrong parameters from HTML to Angular, then you shouldn't be bothered with covering such mistakes in unit tests. You can't force people not to make mistakes :-)