I am trying to spy on $timeout so that I can verify that it has not been called. Specifically, my production code (see below) calls $timeout as a function, not an object:
$timeout(function() { ... })
and not
$timeout.cancel() // for instance
Jasmine, however, requires an object to be spied upon, like this:
spyOn(someObject, '$timeout')
I don't know what 'someObject' would be though.
I am using Angular mocks, if that makes any difference.
Edit: The relevant production code I'm trying to test looks like this:
EventHandler.prototype._updateDurationInOneSecondOn = function (call) {
var _this = this;
var _updateDurationPromise = this._$timeout(function () {
call.duration = new Date().getTime() - call.startTime;
_this._updateDurationInOneSecondOn(call);
}, 1000);
// ... more irrelevant code
}
In the specific test scenario I am trying to assert that $timeout was never called.
Edit 2: Specified clearly that I am using $timeout as a function, not an object.
Ran into the same problem and ended up decorating the $timeout service with a spy.
beforeEach(module(function($provide) {
$provide.decorator('$timeout', function($delegate) {
return sinon.spy($delegate);
});
}));
Wrote more about why this works here.
In angular $timeout is a service that executes/calls a function. The request to "spy" $timeout is a bit odd being the case that what it is doing is executing X function in Y given time. What I would do to spy this services is to "mock" the timeout function and inject it in your controller something like:
it('shouldvalidate time',inject(function($window, $timeout){
function timeout(fn, delay, invokeApply) {
console.log('spy timeout invocation here');
$window.setTimeout(fn,delay);
}
//instead of injecting $timeout in the controller you can inject the mock version timeout
createController(timeout);
// inside your controller|service|directive everything stays the same
/* $timeout(function(){
console.log('hello world');
x = true;
},100); */
var x = false; //some variable or action to wait for
waitsFor(function(){
return x;
},"timeout",200);
...
This code works for me
var element, scope, rootScope, mock = {
timeout : function(callback, lapse){
setTimeout(callback, lapse);
}
};
beforeEach(module(function($provide) {
$provide.decorator('$timeout', function($delegate) {
return function(callback, lapse){
mock.timeout(callback, lapse);
return $delegate.apply(this, arguments);
};
});
}));
describe("when showing alert message", function(){
it("should be able to show message", function(){
rootScope.modalHtml = undefined;
spyOn(mock, 'timeout').and.callFake(function(callback){
callback();
});
rootScope.showMessage('SAMPLE');
expect(rootScope.modalHtml).toBe('SAMPLE');
});
});
Related
I am trying to test javascript method as below,
var spyPostRender = sinon.spy(proxy, "postRender");
var done = assert.async();
proxy.init();
done();
assert.ok(spyPostRender.calledOnce, "postRender() function was called.");
where init() internally calls an ajax service, however when I do this, I am getting below error. Can anybody help me in resolving this issue?
Assertion after the final assert.async was resolved# 85 ms Source:at
Object.QUnit.assert.Assert.ok
(http://code.jquery.com/qunit/qunit-1.17.1.js:1296:8)
FYI - I am using QUnit-1.17.1
Thanks in advance
You are immediately calling the done() function after you call init(), that is incorrect. You should only call the done() method was asynchronous activity has completed (hence the word "done"). The easy way to accomplish this is to add a callback function to your init() method:
proxy.init = function(callback) {
// just using jQuery as an example, could be any framework...
$.ajax({
url: "/some/api/service",
// ...
complete: function() {
callback(/* maybe pass some data back? */);
}
});
};
And then you can pass an anonymous function in when you test it:
QUnit.test("Test the init method", function(assert) {
var spyPostRender = sinon.spy(proxy, "postRender");
var done = assert.async();
proxy.init(function() {
assert.ok(spyPostRender.calledOnce, "postRender() function was called.");
// Notice that we only call done() once everything async is complete!
done();
});
});
First solution is posted by #jakarella, alternative solution without modifying the existing code is using sinon.stub
Or else perhaps use a sinon.stub to override proxy.postRender to perform your async assertions:
var done = assert.async();
var _postRender = proxy.postRender;
var stubPostRender = sinon.stub(proxy, "postRender", function() {
var result = _postRender.apply(this, arguments);
// Do your real assertions BEFORE invoking the final (i.e. you may have multiple) `done` callback
assert.ok(stubPostRender.calledOnce, "postRender() function was called.");
// The final `done` callback MUST be called AFTER all real assertions have been made
done();
return result;
});
proxy.init();
Above code is copied from github solution
https://github.com/jquery/qunit/issues/777
I started to write unit tests for my angular app.
However it seems to me that I use a lot of boilerplate code to init and test the controller.
In this Unit Test I want to test if a model from the scope is sent to the Api when I execute a function.
I needed 20 lines of code for this. This makes it inconvenient to write unit tests that do only one thing.
Do you have any tips on getting the code size to a smaller chunk?
This is my current unit test:
'use strict';
describe('controllers', function(){
beforeEach(module('kronos'));
describe('CustomerSignupCtrl', function() {
it('should send customer to Api on submit', inject(function($controller) {
var scope = {};
var $location = {};
var Api = {
signupCustomer: function(customer) {
expect(customer).toEqual({attrs: "customerdata"});
return {
success: function() { return this; },
error: function() { return this; }
};
}
};
var ctrl = $controller('CustomerSignupCtrl', {
$scope: scope,
$location: location,
Api: Api});
scope.customer = {attrs: "customerdata"};
scope.signup();
}));
});
});
What I don't like in particular are the following points
I need to init the all dependencies and it doesn't matter if I use them or not
The Api returns a promise that I only need because the controller is expecting the promise
I need to init the controller.
How can I make this code shorter and more explicit?
Edit: I just noticed I can ignore the $location Service for this unit test. Great
Edit2: I found out about angular-app, which serves as a good practice example app. There you can find specs with jasmine, which are really nice written.
Use another beforeEach method in your describe scope to set up scope, $location, controller etc, then just change them in your test as you need to. Js is dynamic so all should be fine.
You can also extract each object that you set up into a function so that you can reinitialise them in a test if you need to.
describe('controllers', function(){
beforeEach(module('kronos'));
describe('CustomerSignupCtrl', function() {
var controller, scope, $location, Api;
beforeEach(function(){
scope = {};
$location = {};
Api = {
signupCustomer: function(customer) {
expect(customer).toEqual({attrs: "customerdata"});
return {
success: function() { return this; },
error: function() { return this; }
};
}
};
controller = makeController();
})
function makeController(){
inject(function($controller){
controller = $controller('CustomerSignupCtrl', {
$scope: scope,
$location: location,
Api: Api});
});
}
it('should send customer to Api on submit', function() {
scope.customer = {attrs: "customerdata"};
scope.signup();
});
});
});
You can not shorten your code much. Things like initialization, mocking and assertion have to be done at some place. But you can improve the readability of your code by decoupling initialization and test code. Something like this:
describe('CustomerSignupCtrl', function(){
var controller, scope, location, api;
beforeEach(module('kronos'));
// initialization
beforeEach(inject(function($controller, $rootScope, $location, Api){
scope = $rootScope.$new();
location = $location;
api = Api;
controller = $controller('CustomerSignupCtrl', {
$scope: scope, $location: location, Api: api});
}));
// test
it('should send customer to Api on submit', function() {
scope.customer = {attrs: "customerdata"};
spyOn(api,'signupCustomer').andCallFake(function(customer) {
return {
success: function() { return this; },
error: function() { return this; }
};
});
scope.signup();
expect(api.signupCustomer).toHaveBeenCalledWith(scope.customer);
});
});
I have a factory that uses a promise to resolve a json file.
It should only resolve this file the first time and return the result when called again.
Here is the factory
app.factory('getServerConfig', function ($q, $http, serverConfig) {
return function (fileName) {
var deferred = $q.defer();
if (serverConfig.loaded) {
deferred.resolve("alreadyLoaded");
} else {
$http.get(fileName).then(function (result) {
serverConfig.setConfig(result.data);
deferred.resolve("loaded");
}, function (result) {
deferred.reject();
});
}
return deferred.promise;
};
})
And how I test it :
it('should say hallo to the World', inject(function(getServerConfig) {
var promiseResult;
getServerConfig("server-config.json").then(function (result) {
promiseResult = result;
});
rootScope.$apply();
expect(promiseResult).toBe('loaded');
}));
Unfortunately, it looks likes promiseResult is never set.
Here is a plunker with the code : http://plnkr.co/edit/uRPCjuUDkqPRAv07G5Nx?p=preview
The problem is that $httpBackend demands a flush() (take a look at Flushing HTTP requests), so you can mimic $http asynchronous behavior in your test.
To solve it, store a reference of $httpBackend (or inject it again) and call $httpBack.flush() after the request has been made.
Look:
it('should say hallo to the World', inject(function(getServerConfig, $httpBackend) {
var promiseResult;
getServerConfig("server-config.json").then(function (result) {
promiseResult = result;
});
$httpBackend.flush();
expect(promiseResult).toBe('loaded');
}));
If you gonna use $httpBackend in most of your specs, consider storing it in a local variable, and set it in beforeEach.
I have the following angular controller
function IndexCtrl($scope, $http, $cookies) {
//get list of resources
$http.get(wtm.apiServer + '/v1/developers/me?access_token=' + $cookies['wtmdevsid']).
success(function(data, status, headers, config) {
// snip
}).
error(function(data, status, headers, config) {
// snip
});
$scope.modal = function() {
// snip
}
return;
}
What I am trying to do is mock the get method on the $http service. Here's my unit test code:
describe('A first test suite', function(){
it("A trivial test", function() {
expect(true).toBe(true);
});
});
describe('Apps', function(){
describe('IndexCtrl', function(){
var scope, ctrl, $httpBackend;
var scope, http, cookies = {wtmdevsid:0};
beforeEach(inject(function($injector, $rootScope, $controller, $http) {
scope = $rootScope.$new();
ctrl = new $controller('IndexCtrl', {$scope: scope, $http: $http, $cookies: cookies});
spyOn($http, 'get');
spyOn(scope, 'modal');
}));
it('should create IndexCtrl', function() {
var quux = scope.modal();
expect(scope.modal).toHaveBeenCalled();
expect($http.get).toHaveBeenCalled();
});
});
});
When I run this I get
ReferenceError: wtm is not defined.
wtm is a global object and of course it wouldn't be defined when I run my test because the code that it is declared in is not run when I run my test. What I want to know is why the real $http.get function is being called and how do I set up a spy or a stub so that I don't actually call the real function?
(inb4 hating on globals: one of my coworkers has been tasked with factoring those out of our code :) )
You need to wire up the whenGET method of your $httpBackend in advance of your test. Try setting it up in the beforeEach() function of your test... There is a good example here under "Unit Testing with Mock Backend".
I suggest all globals used the way you described here should be used through the $window service.
All global variables that are available, such as as window.wtm, will also be available on $window.atm.
Then you can stub out your wtm reference completely and spy on it the same way you already described:
var element, $window, $rootScope, $compile;
beforeEach(function() {
module('fooApp', function($provide) {
$provide.decorator('$window', function($delegate) {
$delegate.wtm = jasmine.createSpy();
return $delegate;
});
});
inject(function(_$rootScope_, _$compile_, _$window_) {
$window = _$window_;
$rootScope = _$rootScope_;
$compile = _$compile_;
});
});
Maybe you could create a custom wrapper mock around $httpBackend that handles your special needs.
In detail, Angular overwrites components of the same name with a last-come first-served strategy, this means that the order you load your modules is important in your tests.
When you define another service with the same name and load it after the first one, the last one will be injected instead of the first one. E.g.:
apptasticMock.service("socket", function($rootScope){
this.events = {};
// Receive Events
this.on = function(eventName, callback){
if(!this.events[eventName]) this.events[eventName] = [];
this.events[eventName].push(callback);
}
// Send Events
this.emit = function(eventName, data, emitCallback){
if(this.events[eventName]){
angular.forEach(this.events[eventName], function(callback){
$rootScope.$apply(function() {
callback(data);
});
});
};
if(emitCallback) emitCallback();
}
});
This service offers the exact same interface and behaves exactly like the original one except it never communicates via any socket. This is the service we want to use for testing.
With the load sequence of angular in mind, the tests then look like this:
describe("Socket Service", function(){
var socket;
beforeEach(function(){
module('apptastic');
module('apptasticMock');
inject(function($injector) {
socket = $injector.get('socket');
});
});
it("emits and receives messages", function(){
var testReceived = false;
socket.on("test", function(data){
testReceived = true;
});
socket.emit("test", { info: "test" });
expect(testReceived).toBe(true);
});
});
The important thing here is that module('apptasticMock') gets executed after module('apptastic'). This overwrites the original socket implementation with the mocked one. The rest is just the normal dependency injection procedure.
This article I wrote could be interesting for you, as it goes into further details.
I've got a fairly straightforward function which returns a jQuery .ajax() promise as such:
CLAW.controls.validateLocation = function(val, $inputEl) {
return $.ajax({
url: locationServiceUrl + 'ValidateLocation/',
data: {
'locationName': val
},
beforeSend: function() {
$inputEl.addClass('busy');
}
}).done(function(result) {
// some success clauses
}).fail(function(result) {
// some failure clauses
}).always(function() {
// some always clauses
});
}
For the most part, this new promises interface works like a dream, and eliminating callback pyramids when using jQuery's .ajax() is great. However, I cannot for the life of me figure out how to properly test these promises using Jasmine and/or Sinon:
All of Sinon's documentation assumes you're using old-school
callbacks; I don't see a single example of how to use it with
promises/deferreds
When attempting to use a Jasmine or Sinon spy to spy on $.ajax, the
spy is effectively overwriting the promise, so its done, fail,
and always clauses no longer exist on the ajax function, so the promise never resolves and tosses an error instead
I'd really just love an example or two of how to test these new jQuery .ajax() promises with the aforementioned testing libs. I've scoured the 'net fairly intensely and haven't really dredged up anything on doing so. The one resource I did find mentioned using Jasmine.ajax, but I'd like to avoid that if possible, seeing as Sinon provides most of the same capabilities out-of-the-box.
It is not that complex actually. It suffices to return a promise and resolve it according to your case.
For example:
spyOn($, 'ajax').andCallFake(function (req) {
var d = $.Deferred();
d.resolve(data_you_expect);
return d.promise();
});
for a success, or
spyOn($, 'ajax').andCallFake(function (req) {
var d = $.Deferred();
d.reject(fail_result);
return d.promise();
});
for a failure.
For Jasmine 2.0 the syntax has changed slightly:
spyOn($, 'ajax').and.callFake(function (req) {});
the method .andCallFake() does not exist in Jasmine 2.0
something along these lines / with sinon and jQuery deferreds
ajaxStub = sinon.stub($, "ajax");
function okResponse() {
var d = $.Deferred();
d.resolve( { username: "testuser", userid: "userid", success: true } );
return d.promise();
};
function errorResponse() {
var d = $.Deferred();
d.reject({},{},"could not complete");
return d.promise();
};
ajaxStub.returns(okResponse());
ajaxStub.returns(errorResponse());
Here's a simpler approach with just javascript.
quoteSnapshots: function (symbol, streamId) {
var FakeDeferred = function () {
this.error = function (fn) {
if (symbol.toLowerCase() === 'bad-symbol') {
fn({Error: 'test'});
}
return this;
};
this.data = function (fn) {
if (symbol.toLowerCase() !== 'bad-symbol') {
fn({});
}
return this;
};
};
return new FakeDeferred();
}
The if statements inside of each callback are what I use in my test to drive a success or error execution.
The solution given by #ggozad won't work if you use things like .complete().
But, hooray, jasmine made a plugin to do exactly this: http://jasmine.github.io/2.0/ajax.html
beforeEach(function() {
jasmine.Ajax.install();
});
afterEach(function() {
jasmine.Ajax.uninstall();
});
//in your tests
expect(jasmine.Ajax.requests.mostRecent().url).toBe('/some/cool/url');