Suppose that I have a directive like:
.directive('myDir', function(TemplateHandler){
return {
...
controller: function(ExploreCmd){
this.foo = {
bar: function(){...}
};
this.foo.bar();
}
}
});
And I want to test that when the directive is loaded the this.foo.bar() function has been called, how can I achieve that?
I tried with:
beforeEach(inject(function($compile, $rootScope){
scope = $rootScope.$new();
element = angular.element('<js-shell></js-shell>');
$compile(element)(scope);
scope.$digest();
isolatedScope = element.isolateScope().vm;
spyOn(isolatedScope.foo, 'bar');
}));
it('should register the explore command', () => {
expect(isolatedScope.foo.bar).toHaveBeenCalled();
});
But the problem is that spyOn(isolatedScope.foo, 'bar'); is called after the isolatedScope.foo.bar has been called, so the test fail.
I don't think you can in this situation. There is no moment in time between when foo is created and when foo.bar is invoked where you could get hold of it to replace foo.bar with a spy.
By the time your spyOn function runs, foo.bar() has been called and returned.
If foo.bar has a side effect like setting foo.baz to true, you can look at that.
Related
How do I mock the timeout call, here?
$scope.submitRequest = function () {
var formData = getData();
$scope.form = JSON.parse(formData);
$timeout(function () {
$('#submitForm').click();
}, 2000);
};
I want to see timeout has been called with the correct function.
I would like an example of the spyon function mocking $timeout.
spyOn(someObject,'$timeout')
First of all, DOM manipulation should only be performed in directives.
Also, it's better to use angular.element(...), than $(...).
Finally, to do this, you can expose your element's click handler to the scope, spy on it, and check if that handler has been called:
$timeout.flush(2000);
$timeout.verifyNoPendingTasks();
expect(scope.myClickHandler).toHaveBeenCalled();
EDIT:
since that's a form and there is no ng-click handler, you can use ng-submit handler, or add a name to your form and do:
$timeout.flush(2000);
$timeout.verifyNoPendingTasks();
expect(scope.formName.$submitted).toBeTruthy();
$timeout can be spied or mocked as shown in this answer:
beforeEach(module('app', ($provide) => {
$provide.decorator('$timeout', ($delegate) => {
var timeoutSpy = jasmine.createSpy().and.returnValue($delegate);
// methods aren't copied automatically to spy
return angular.extend(timeoutSpy, $delegate);
});
}));
There's not much to test here, since $timeout is called with anonymous function. For testability reasons it makes sense to expose it as scope/controller method:
$scope.submitFormHandler = function () {
$('#submitForm').click();
};
...
$timeout($scope.submitFormHandler, 2000);
Then spied $timeout can be tested:
$timeout.and.stub(); // in case we want to test submitFormHandler separately
scope.submitRequest();
expect($timeout).toHaveBeenCalledWith(scope.submitFormHandler, 2000);
And the logic inside $scope.submitFormHandler can be tested in different test.
Another problem here is that jQuery doesn't work well with unit tests and requires to be tested against real DOM (this is one of many reasons why jQuery should be avoided in AngularJS applications when possible). It's possible to spy/mock jQuery API like shown in this answer.
$(...) call can be spied with:
var init = jQuery.prototype.init.bind(jQuery.prototype);
spyOn(jQuery.prototype, 'init').and.callFake(init);
And can be mocked with:
var clickSpy = jasmine.createSpy('click');
spyOn(jQuery.prototype, 'init').and.returnValue({ click: clickSpy });
Notice that it's expected that mocked function will return jQuery object for chaining with click method.
When $(...) is mocked, the test doesn't require #submitForm fixture to be created in DOM, this is the preferred way for isolated unit test.
Create mock for $timeout provider:
var f = () => {}
var myTimeoutProviderMock = () => f;
Use it:
beforeEach(angular.mock.module('myModule', ($provide) => {
$provide.factory('$timeout', myTimeoutProviderMock);
}))
Now you can test:
spyOn(f);
expect(f).toHaveBeenCalled();
P.S. you'd better test result of function in timeout.
Assuming that piece of code is within the controller or being created in the test by $controller, then $timeout can be passed in the construction parameter. So you could just do something like:
var timeoutStub = sinon.stub();
var myController = $controller('controllerName', timeoutStub);
$scope.submitRequest();
expect(timeoutStub).to.have.been.called;
Unit Tesitng $timeout with flush delay
You have to flush the queue of the $timeout service by calling $timeout.flush()
describe('controller: myController', function(){
describe('showAlert', function(){
beforeEach(function(){
// Arrange
vm.alertVisible = false;
// Act
vm.showAlert('test alert message');
});
it('should show the alert', function(){
// Assert
assert.isTrue(vm.alertVisible);
});
it('should hide the alert after 5 seconds', function(){
// Act - flush $timeout queue to fire off deferred function
$timeout.flush();
// Assert
assert.isFalse(vm.alertVisible);
});
})
});
Please checkout this link http://jasonwatmore.com/post/2015/03/06/angularjs-unit-testing-code-that-uses-timeout
I totally agree with Frane Poljak's answer. You should surely follow his way. Second way to do it is by mocking $timeout service like below:
describe('MainController', function() {
var $scope, $timeout;
beforeEach(module('app'));
beforeEach(inject(function($rootScope, $controller, $injector) {
$scope = $rootScope.$new();
$timeout = jasmine.createSpy('$timeout');
$controller('MainController', {
$scope: $scope,
$timeout: $timeout
});
}));
it('should submit request', function() {
$scope.submitRequest();
expect($timeout).toHaveBeenCalled();
});
Here is the plunker having both approaches: http://plnkr.co/edit/s5ls11
I have an unit test that look like this:
describe('myDirective:', function () {
var rootScope, compile;
beforeEach(module('myApp'));
beforeEach(function () {
//inject dependencies
inject(function ($compile, $rootScope) {
rootScope = $rootScope;
compile = $compile;
});
});
it('first test', function () {
var scope = rootScope.$new();
var element = angular.element('<my-directive><div id="myid" style="height:300px"></div></my-directive>');
element.appendTo(document.body);
element = compile(element)(scope);
console.log("#myid height: "+$("#myid").height());
expect($("#myid").height()).toBe(300);
});
it('second test', function () {
var scope = rootScope.$new();
var element = angular.element('<my-directive><div id="myid" style="height:400px"></div></my-directive>');
element.appendTo(document.body);
element = compile(element)(scope);
console.log("#myid height: "+$("#myid").height());
expect($("#myid").height()).toBe(400);
});
});
http://jsfiddle.net/bLg4veso/
It pass the first test, but fail on second test. The second test will always return the height as being set in the first test. How can I reset it?
Since you are appending the element to the body, you need to manually remove it too.
Add this line to end of each test
element.remove();
and it should work.
Alternatively, you could use the afterEach() function available in Jasmine. It seems to be equivalent to tearDown() from Python's unittest module, in that afterEach() is executed after each test.
By using the afterEach() function, you only have to specify this clean up work once.
You can read more about afterEach() (as well as similar functions like beforeEach(), etc) on Jasmine's introduction documentation page.
I'm using jasmine as testframework and I've the following Controller I want to test. And I allways have a Init() function where I place my initialization calls for this Controller.
Now I want to test if the Init function was called when the controller was initialized.
function UnitTestsCtrl() {
var that = this;
this.Init();
}
UnitTestsCtrl.prototype.Init = function() {
var that = this;
//Some more Stuff
}
angular.module("unitTestsCtrl", [])
.controller("unitTestsCtrl", UnitTestsCtrl);
But I was not able to check if the Init function was called on controller creation. I know my example doesn't work because the spy is set on the Init function after creation.
describe('Tests Controller: "UnitTestsCtrl"', function() {
var ctrl;
beforeEach(function() {
module('app.main');
inject(function ($controller) {
ctrl = $controller('unitTestsCtrl', {});
});
});
it('Init was called on Controller initialize', function () {
//thats not working
spyOn(ctrl, 'Init');
expect(ctrl.Init).toHaveBeenCalled();
});
});
Solution:
Create the spy on the Original Prototype in the beforeEach function
beforeEach(function() {
module('app.main');
spyOn(UnitTestsCtrl.prototype, 'Init');
inject(function ($controller) {
ctrl = $controller('unitTestsCtrl', {});
});
});
it('Init was called on Controller initialize', function () {
expect(ctrl.Init).toHaveBeenCalled();
});
The way it is, you cannot and you really do not need to as well. The reason you cannot is because you are calling init() on the controller constructor, i.e on instantiation, which happens when you call $controller service to instantiate the controller in your test. So you are setting up spy too late. You probably do not need to, because if the controller is instantiated init method would have been called for sure. But how ever if you are making any specific service/dependency calls inside init, you can spy on those mocks and set up expectations.
Your expectation says: Service Call executed so create a spy for that service and set up expectation.
example:
var myService = jasmine.createSpyObj('myService', ['someCall']);
myService.someCall.and.returnValue($q.when(someObj));
//...
ctrl = $controller('unitTestsCtrl', {'myService':myService});
and set the expectation on the method someCall of myService.
expect(myService.someCall).toHaveBeenCalled();
If you really want to spy on init, then you would need to have access to UnitTestsCtrl constructor in the spec and you would need to set spy on its prototype method init before instantiating.
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.
I'm struggling unit testing a controller that watches a couple variables. In my unit tests, I can't get the callback for the $watch function to be called, even when calling scope.$digest(). Seems like this should be pretty simple, but I'm having no luck.
Here's what I have in my controller:
angular.module('app')
.controller('ClassroomsCtrl', function ($scope, Classrooms) {
$scope.subject_list = [];
$scope.$watch('subject_list', function(newValue, oldValue){
if(newValue !== oldValue) {
$scope.classrooms = Classrooms.search(ctrl.functions.search_params());
}
});
});
And here's my unit test:
angular.module('MockFactories',[]).
factory('Classrooms', function(){
return jasmine.createSpyObj('ClassroomsStub', [
'get','save','query','remove','delete','search', 'subjects', 'add_subject', 'remove_subject', 'set_subjects'
]);
});
describe('Controller: ClassroomsCtrl', function () {
var scope, Classrooms, controllerFactory, ctrl;
function createController() {
return controllerFactory('ClassroomsCtrl', {
$scope: scope,
Classrooms: Classrooms
});
}
// load the controller's module
beforeEach(module('app'));
beforeEach(module('MockFactories'));
beforeEach(inject(function($controller, $rootScope, _Classrooms_ ){
scope = $rootScope.$new();
Classrooms = _Classrooms_;
controllerFactory = $controller;
ctrl = createController();
}));
describe('Scope: classrooms', function(){
beforeEach(function(){
Classrooms.search.reset();
});
it('should call Classrooms.search when scope.subject_list changes', function(){
scope.$digest();
scope.subject_list.push(1);
scope.$digest();
expect(Classrooms.search).toHaveBeenCalled();
});
});
});
I've tried replacing all the scope.$digest() calls with scope.$apply() calls. I've tried calling them 3 or 4 times, but I can't get the callback of the $watch to get called.
Any thoughts as to what could be going on here?
UPDATE:
Here's an even simpler example, that doesn't deal with mocks, stubbing or injecting factories.
angular.module('edumatcherApp')
.controller('ClassroomsCtrl', function ($scope) {
$scope.subject_list = [];
$scope.$watch('subject_list', function(newValue, oldValue){
if(newValue !== oldValue) {
console.log('testing');
$scope.test = 'test';
}
});
And unit test:
it('should set scope.test', function(){
scope.$digest();
scope.subject_list.push(1);
scope.$digest();
expect(scope.test).toBeDefined();
});
This fails too with "Expected undefined to be defined." and nothing is logged to the console.
UPDATE 2
2 more interesting things I noted.
It seems like one problem is that newValue and oldValue are the same when the callback is called. I logged both to the console, and they are both equal to []. So, for example, if I change my $watch function to look like this:
$scope.$watch('subject_list', function(newValue, oldValue){
console.log('testing');
$scope.test = 'test';
});
the test passes fine. Not sure why newValue and oldValue aren't getting set correctly.
If I change my $watch callback function to be a named function, and just check to see if the named function is ever called, that fails as well. For example, I can change my controller to this:
$scope.update_classrooms = function(newValue, oldValue){
$scope.test = 'testing';
console.log('test');
};
$scope.watch('subject_list', $scope.update_classrooms);
And change my test to this:
it('should call update_classrooms', function(){
spyOn(scope,'update_classrooms').andCallThrough();
scope.$digest();
scope.subject_list.push(1);
scope.$digest();
expect(scope.update_classrooms).toHaveBeenCalled();
});
it fails with "Expected spy update_classrooms to have been called."
In this case, update_classrooms is definitely getting called, because 'test' gets logged to the console. I'm baffled.
I just ran into this problem within my own code base and the answer turned out to be that I needed a scope.$digest() call right after instantiating the controller. Within your tests you have to call scope.$digest() manually after each change in watched variables. This includes after the controller is constructed to record the initial watched value(s).
In addition, as Vitall specified in the comments, you need $watchCollection() for collections.
Changing this watch in your simple example resolves the issue:
$scope.$watch('subject_list', function(newValue, oldValue){
if(newValue !== oldValue) {
console.log('testing');
$scope.test = 'test';
}
});
to:
$scope.$watchCollection('subject_list', function(newValue, oldValue){
if(newValue !== oldValue) {
console.log('testing');
$scope.test = 'test';
}
});
I made a jsfiddle to demonstrate the difference:
http://jsfiddle.net/ydv8k4zy/ - original with failing test - fails due to using $watch, not $watchCollection
http://jsfiddle.net/ydv8k4zy/1/ - functional version
http://jsfiddle.net/ydv8k4zy/2/ - $watchCollection fails without the initial scope.$digest.
If you play around With console.log on the second failing item you'll see that the watch is called with the same value for old and new after the scope.$digest() (line 25).
The reason that this isn't testable is because the way that the functions are passed into the watchers vs the way that spyOn works. The spyOn method takes the scope object and replaces the original function on that object with a new one... but most likely you passed the whole method by reference into the $watch, that watch still has the old method. I created this non-AngularJS example of the same thing:
https://jsfiddle.net/jonhartmann/9bacozmg/
var sounds = {};
sounds.beep = function () {
alert('beep');
};
document.getElementById('x1').addEventListener('click', sounds.beep);
document.getElementById('x2').addEventListener('click', function () {
sounds.beep = function () {
alert('boop');
};
});
document.getElementById('x3').addEventListener('click', function () {
sounds.beep();
});
The difference between the x1 handler and the x3 handler is that in the first binding the method was passed in directly, in the second the beep/boop method just calls whatever method is on the object.
This is the same thing I ran into with my $watch - I'd put my methods on a "privateMethods" object, pass it out and do a spyOn(privateMethods, 'methodname'), but since my watcher was in the format of $scope.$watch('value', privateMethods.methodName) it was too late - the code would get executed by my spy wouldn't work. Switching to something like this skipped around the problem:
$scope.$watch('value', function () {
privateMethods.methodName.apply(null, Array.prototype.slice.call(arguments));
});
and then the very expected
spyOn(privateMethods, 'methodName')
expect(privateMethods.methodName).toHaveBeenCalled();
This works because you're no longer passing privateMethods.methodName by reference into the $watch, you're passing a function that in turn executes the "methodName" function on "privateMethods".
Your problem is that you need to call $scope.$watchCollection instead of plain $watch.
$watch will only respond to assignments (i.e scope.subject_list = ['new item']).
In addition to assignments, $watchCollection will respond to changes to lists (push/splice).