Learning jasmine for the first time and I am stuck on this error when trying to test the focus() functionality in an angular service.
Here is the service:
myApp.service('MyService', function($timeout, $window) {
var service = {
focusElem: focusElem
};
return service;
function focusElem(id) {
console.log('id of element is = ', id);
if (id) {
$timeout(function() {
var element = $window.document.getElementById(id);
console.log('element is = ', element);
if (element) {
element.focus();
}
});
}
};
});
Here is my spec file
describe('myApp', function() {
var element, dummyElement;
beforeEach(function() {
// Initialize myApp injector
module('myApp');
// Inject instance of service under test
inject(function($injector) {
MyServiceObj = $injector.get('MyService');
});
element = angular.element('<input id="firstName" name="firstName"/>');
dummyElement = document.createElement('input');
dummyElement.setAttribute('id', 'lastName');
});
it('should have focus if the focus Service is used on an element', function() {
console.info('------------------');
spyOn(element[0], 'focus');
spyOn(dummyElement, 'focus');
MyServiceObj.focusElem(dummyElement.getAttribute('id'));
expect(dummyElement.focus).toHaveBeenCalled();
});
});
My error:
myApp should have focus if the focus Service is used on an element
Expected spy focus to have been called.
Error: Expected spy focus to have been called.
If you are using ngMock many services are changed so they can be controlled in a synchronous manner within test code to give you more control over the flow.
One of the affected services is $timeout.
The function passed to $timeout inside your service will not execute in your test unless you tell it to.
To tell it to execute use $timeout.flush() like this:
spyOn(element[0], 'focus');
spyOn(dummyElement, 'focus');
MyServiceObj.focusElem(dummyElement.getAttribute('id'));
$timeout.flush();
expect(dummyElement.focus).toHaveBeenCalled();
Note that you need a reference to the $timeout service:
var element, dummyElement, $timeout;
beforeEach(function() {
module('myApp');
inject(function($injector, _$timeout_) {
MyServiceObj = $injector.get('MyService');
$timeout = _$timeout_;
});
The next problem is due to the following line in your service:
var element = $window.document.getElementById(id);
The elements you create in your test are never attached to the DOM, so the service will not find them.
The easiest solution is to just attach your elements to the DOM. In this case it's important that you remove them manually after the test, since Jasmine uses the same DOM for your entire test suite.
For example:
it('should have focus if the focus Service is used on an element', function() {
var body = angular.element(document.body);
body.append(element);
body.append(dummyElement);
spyOn(element[0], 'focus');
spyOn(dummyElement, 'focus');
MyServiceObj.focusElem(dummyElement.getAttribute('id'));
$timeout.flush();
expect(dummyElement.focus).toHaveBeenCalled();
element.remove();
dummyElement.remove();
});
Demo: http://plnkr.co/edit/F8xqfYYQGa15rwuPPbN2?p=preview
Now, attaching and removing elements to the DOM during unit tests are not always a good thing to do and can get messy.
There are other ways to handle it, for example by spying on getElementById and controlling the return value or by mocking an entire document. I won't go into that here however as I'm sure there are examples of it around here already.
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 made a simple demo of a factory and I am trying to test this using jasmine. I am able to run the test but I am using the spyOn method. I would rather use jasmine.createSpy or jasmine.createSpyObj to do the same test. Could someone help me to refactor my code so that uses these methods instead in my example?
http://plnkr.co/edit/zdfYdtWbnQz22nEbl6V8?p=preview
describe('value check',function(){
var $scope,
ctrl,
fac;
beforeEach(function(){
module('app');
});
beforeEach(inject(function($rootScope,$controller,appfactory) {
$scope = $rootScope.$new();
ctrl = $controller('cntrl', {$scope: $scope});
fac=appfactory;
spyOn(fac, 'setValue');
fac.setValue('test abc');
}));
it('test true value',function(){
expect(true).toBeTruthy()
})
it('check message value',function(){
expect($scope.message).toEqual(fac.getValue())
})
it("tracks that the spy was called", function() {
expect(fac.setValue).toHaveBeenCalled();
});
it("tracks all the arguments of its calls", function() {
expect(fac.setValue).toHaveBeenCalledWith('test abc');
});
})
update
angular.module('app',[]).factory('appfactory',function(){
var data;
var obj={};
obj.getValue=getValue;
obj.setValue=setValue;
return obj;
function getValue(){
return data;
}
function setValue(datavalue){
data=datavalue;
}
}).controller('cntrl',function($scope,appfactory){
appfactory.setValue('test abc');
$scope.message=appfactory.getValue()
})
I have changed your plunkr:
spy = jasmine.createSpy('spy');
fac.setValue = spy;
Edit
In Jasmine, mocks are referred to as spies. There are two ways to
create a spy in Jasmine: spyOn() can only be used when the method
already exists on the object, whereas jasmine.createSpy() will return
a brand new function.
Found the information here. The link has a lot more information about creating spies.
As said in the comments, you have absolutely no need for spies to test such a service. If you had to write the documentation for your service: you would say:
setValue() allows storing a value. This value can then be retrieved by calling getValue().
And that's what you should test:
describe('appfactory service',function(){
var appfactory;
beforeEach(module('app'));
beforeEach(inject(function(_appfactory_) {
appfactory = _appfactory_;
}));
it('should store a value and give it back',function() {
var value = 'foo';
appfactory.setValue(value);
expect(appfactory.getValue()).toBe(value);
});
});
Also, your service is not a factory. A factory is an object that is used to create things. Your service doesn't create anything. It is registered in the angular module using a factory function. But the service itself is not a factory.
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 have the following directive to autofocus a field:
.directive('ngAutofocus', function ($timeout) {
return {
restrict: 'A',
link: function (scope, elm) {
$timeout(function () {
elm[0].focus();
});
}
};
}
How would I unit test this? I tried several things like the following selector but they all return errors or false:
console.log($(elm[0]).is(':focus'));
My unit test is set up like this:
elm = angular.element('<input type="text" name="textfield1" ng-autofocus>');
$scope.$digest();
$compile(elm)($scope);
I figured it out, and it was pretty obvious actually;
it('should set the focus on timeout', function () {
spyOn(elm[0],'focus');
$timeout.flush();
expect(elm[0].focus).toHaveBeenCalled();
})
My problem was two-fold:
I wasn't calling the timeout flush function so timeout wasn't occuring, and
I was trying to look at the focus attribute of the element whereas just looking at the call of the focus() function is much more like unit-testing. The focus attribute is something that really belongs to the e2e testing territory.
You can use document.activeElement to check focus. The only downside being that the HTML needs to be added to the document body for this to work.
https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement
A more verbose solution below, which allows testing (spying on) the focus that runs immediately (i.e. no $timeout or other event). The key is to first render a DOM element before $compile runs:
'use strict';
describe('Testing the focus call from the link function', function () {
var $compile;
var $rootScope;
beforeEach(angular.mock.module('auto-focus-module'));
beforeEach(inject(function (_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
}));
it('should automatically focus when calling the link function', function () {
var $scope = $rootScope.$new();
// create an uncompiled DOM element so we can bind the focus spy
var rawEl = angular.element('<input auto-focus-directive>');
// set the spy
spyOn(rawEl[0], 'focus');
// compile the rawEl so that compile and link functions run
$compile(rawEl)($scope);
expect(rawEl[0].focus).toHaveBeenCalled();
});
});
With a directive and link function that could look like:
(function () {
'use strict';
angular.module('auto-focus-module')
.directive('autoFocusDirective', autoFocusDirective);
function autoFocusDirective () {
return {
link: link
};
function link (scope, elem) {
elem[0].focus();
}
}
})();
You should use the angular.element api - jQuery lite - and use the method triggerHandler().
it('should have focus', function() {
elm.triggerHandler('focus');
expect(elm).toBeInFocus() //PSEUDO CODE - you will need to see how this can be tested
}
http://docs.angularjs.org/api/ng/function/angular.element
http://api.jquery.com/triggerhandler/
Potential Area for some testing focus knowledge:
https://shanetomlinson.com/2014/test-element-focus-javascript
Also you concerning your unit test - you don't need to append the element to the body, it's possible to test without that.
I want to test my AngularJS alert service like this (using Mocha and Chai):
describe('service', function() {
var alertService;
var $rootScope;
beforeEach(module('components.services'));
beforeEach(inject(function(_alertService_, _$rootScope_) {
alertService = _alertService_;
$rootScope = _$rootScope_;
}));
describe('alertService', function() {
it('should start with zero alerts', function() {
$rootScope.should.have.property('alerts').with.length(0);
});
it('should add an alert of type danger', function() {
alertService.add('danger', 'Test Alert!');
$rootScope.should.have.property('alerts').with.length(1);
});
it('should add an alert of type warning', function() {
alertService.add('warning', 'Test Alert!');
$rootScope.should.have.property('alerts').with.length(2);
});
it('should close via the alert', function() {
var alert = $rootScope.alerts[0];
alert.should.have.property('close');
alert.close();
$rootScope.should.have.property('alerts').with.length(1);
});
});
});
However, the beforeEach method is resetting the rootScope before each test (I kinda expected it to run before each "describe", not each "it"), so counting the number of alerts doesn't work.
What's the best way around this? Have multiple asserts within one big "it"? I'm quite new to unit testing in general and in Javascript in particular so any explanation is very welcome.
it's resetting the rootScope because you have it declared as a variable and not actually injected into the method...try passing in $rootScope and delete the var declaration of it.