asynFn(url, callback)
This function takes a url and fires some xhr requests, then uses callback(result) to send back processed result. How should I test it?
(I've run the asynFn directly in Chrome and it worked fine.)
I tried to use jasmine-ajax to stub the request, but the expect did't work.
describe('a test', function() {
var callback
beforeAll(function() {
jasmine.Ajax.install()
jasmine.Ajax.stubRequest('fake/path1').andReturn({
status: 200,
contentType: 'text/plain',
responseText: 'yay'
})
jasmine.Ajax.stubRequest('fake/path2').andReturn({
status: 200,
contentType: 'text/plain',
responseText: 'yay2'
})
// ...
})
afterAll(function() {
jasmine.Ajax.uninstall()
})
beforeEach(function() {
callback = jasmine.createSpy('sendResponse')
})
it('a spec', function() {
asynFn('input string', callback)
expect(jasmine.Ajax.requests.mostRecent().url).toBe('fake/path2')
expect(callback).toHaveBeenCalled() // faild
})
})
What am I missing here?
The problem is that asynFn is asynchronous and the callback y called after the expect sentence is executed.
Think you test like a history.
Subject Under Testing (describe)
When asynFn is executed (beforeEach)
Then: A method or callback should be called (it)
Change your code to:
beforeEach(function() {
callback = jasmine.createSpy('sendResponse');
asynFn('input string', callback);
});
afterEach(function() {
callback = null;
});
it('a spec', function() {
expect(jasmine.Ajax.requests.mostRecent().url).toBe('fake/path2')
expect(callback).toHaveBeenCalled() // faild
})
if The first doesn't work, try this:
beforeEach(function(done) {
callback = jasmine.createSpy('sendResponse');
asynFn('input string', function() {
callback();
done(); //<-- This tells jasmine tha async beforeEach is finished
});
});
Related
Prior to adding a promise, this code/spec was successfully passing
// Code
function getAvailability() {
$.ajax({
type:"POST",
url: "/get-availability",
dataType:"json",
contentType:"application/json",
data: params,
success: function() {
// ...
},
error: function() {
console.log(">> in error")
catalogDOM.updateAvailabilityForItem(0)
}
})
}
// Spec
describe("if ajax fails", function() {
beforeEach(function() {
ajaxSpy = spyOn($, "ajax")
ajaxSpy.and.callFake(function(e) {
e.error()
})
spyOn(catalogDOM, "updateAvailabilityForItem")
getAvailability()
})
it("should call updateAvailabilityForItem with 0", function() {
expect(catalogDOM.updateAvailabilityForItem).toHaveBeenCalledWith(0)
}
})
// Console output
>> in error
But then I made the ajax fire after another async function ran. I thought I spied on things correctly, and in fact, the console.log continues to indicate that the code is running. In fact, if I mocked a return value for like so spyOn(catalogDOM, "updateAvailabilityForItem").and.returnValue("works") and then in the code wrote: console.log(catalogDOM.updateAvailabilityForItem(0)), then the log does output "works"!
Yet the spec fails. Why?
// Code
function getAvailability() {
//////// START NEW LINE
utilityOrders.checkElement('#overlay').then((selector) => {
//////// END NEW LINE
$.ajax({
type:"POST",
url: "/get-availability",
dataType:"json",
contentType:"application/json",
data: params,
success: function() {
// ...
},
error: function() {
console.log(">> in error")
catalogDOM.updateAvailabilityForItem(0)
}
})
})
}
// Spec
describe("if ajax fails", function() {
beforeEach(function() {
//////// START NEW LINE
resolved_promise = new Promise(function(resolve, reject) { resolve() })
spyOn(utilityOrders,"checkElement").and.returnValue(resolved_promise)
//////// END NEW LINE
ajaxSpy = spyOn($, "ajax")
ajaxSpy.and.callFake(function(e) {
e.error()
})
spyOn(catalogDOM, "updateAvailabilityForItem")
getAvailability()
})
it("should call updateAvailabilityForItem with 0", function() {
expect(catalogDOM.updateAvailabilityForItem).toHaveBeenCalledWith(0)
}
})
// Console output
>> in error
Try making the following changes:
// Code
// !! add async here so we have control to wait until it goes inside of the .then before continuing !!
async function getAvailability() {
//////// START NEW LINE
utilityOrders.checkElement('#overlay').then((selector) => {
//////// END NEW LINE
$.ajax({
type:"POST",
url: "/get-availability",
dataType:"json",
contentType:"application/json",
data: params,
success: function() {
// ...
},
error: function() {
console.log(">> in error")
catalogDOM.updateAvailabilityForItem(0)
}
})
})
}
// Spec
describe("if ajax fails", function() {
// !! add async and done argument to call when we are done with this function !!
beforeEach(async function(done) {
//////// START NEW LINE
resolved_promise = new Promise(function(resolve, reject) { resolve() })
spyOn(utilityOrders,"checkElement").and.returnValue(resolved_promise)
//////// END NEW LINE
ajaxSpy = spyOn($, "ajax")
ajaxSpy.and.callFake(function(e) {
e.error()
})
spyOn(catalogDOM, "updateAvailabilityForItem")
// await getAvailability so the promise is resolved before proceeding (then is called)
await getAvailability();
// call done to let Jasmine know you're done with the test
done();
})
it("should call updateAvailabilityForItem with 0", function() {
expect(catalogDOM.updateAvailabilityForItem).toHaveBeenCalledWith(0)
}
The above modifications should hopefully fix it for you. Dealing with promises and tests is difficult because sometimes in the test you have to tell Jasmine that the promises that were created in the code that is being tested, wait for them to complete before doing assertions.
I have built up a javascript file that starts with:
var myApp = function () {
var CLIENT_ID = 'xxxxxxx';
var DISCOVERY_DOCS = ["https://www.googleapis.com/discovery/v1/apis/gmail/v1/rest"];
var SCOPES = 'https://www.googleapis.com/auth/gmail.readonly https://www.googleapis.com/auth/analytics.readonly https://www.googleapis.com/auth/drive.readonly';
var authorizeButton = document.getElementById('authorize-button');
return {
The functions are declared like:
getSent: function(callback) {
var request = gapi.client.gmail.users.messages.list({
'userId': 'me',
'labelIds': 'SENT',
'maxResults': 10
});
request.execute(function(response) {
$.each(response.messages, function() {
var messageRequest = gapi.client.gmail.users.messages.get({
'userId': 'me',
'id': this.id
});
messageRequest.execute(myApp.appendMessageRow);
});
});
},
And then run through a single function that calls others:
myApp.init();
How can I defer a function to be run after my google request function getsent() has been fully completed. I have tried using callbacks to another function and it runs but it runs while the getsent() is still being executed. Can I use the jQuery method defered to run the callback when done?
I have tried:
myApp.getSent(myApp.gMailSyncComplete()); // Runs early
and I tried:
myApp.getSent().done(myApp.gMailSyncComplete()); // no jQuery done defined
Some important errors in your code:
You are creating a function getSent that accepts a callback, but you are not calling the callback at all so it won't get executed ever. You should execute the callback when everything else is done.
You are not waiting for all message requests to be completed. You can use a library like async which has the method map to be able to execute all requests in parallel and wait for all of them to be completed before calling the callback.
With these two things in mind, and using async, this would be an example of the resulting code:
getSent: function (callback) {
var request = gapi.client.gmail.users.messages.list({
'userId': 'me',
'labelIds': 'SENT',
'maxResults': 10
})
request.execute(function (response) {
async.map(response.messages, function (msg, cb) {
var messageRequest = gapi.client.gmail.users.messages.get({
'userId': 'me',
'id': msg.id
})
messageRequest.execute(function (result) {
myApp.appendMessageRow(result)
cb()
})
}, function (err) {
if (err) throw err
callback()
})
})
}
Lastly, when invoking this function, keep in mind that the callback parameter must be a function.
To make things clear, let's translate the code you wrote, myApp.getSent(myApp.gMailSyncComplete()), into an equivalent structure:
var callback = myApp.gMailSyncComplete()
myApp.getSent(callback)
When you do this, you are not passing the function but the result of the function, because you are executing it. That's why it gets executed immediately. The correct way to do this would be the following:
var callback = myApp.gMailSyncComplete
myApp.getSent(callback)
Or, in your one-liner example, myApp.getSent(myApp.gMailSyncComplete)
you can use javascript promise.
function testPromise() {
let p1 = new Promise(
// The resolver function is called with the ability to resolve or
// reject the promise
(resolve, reject) => {
/*your async function*/
}
);
// defined what to do when the promise is resolved with the then() call,
// and what to do when the promise is rejected with the catch() call
p1.then(
// Log the fulfillment value
function(val) {
log.insertAdjacentHTML('beforeend', val +
') Promise fulfilled (<small>Async code terminated</small>)<br/>');
})
.catch(
// Log the rejection reason
(reason) => {
console.log('Handle rejected promise ('+reason+') here.');
});
}
You could use jQuery promise(), check the example below.
getSent: function() {
var request = gapi.client.gmail.users.messages.list({
'userId': 'me',
'labelIds': 'SENT',
'maxResults': 10
});
request.execute(function(response) {
$.each(response.messages, function() {
var messageRequest = gapi.client.gmail.users.messages.get({
'userId': 'me',
'id': this.id
});
messageRequest.execute(myApp.appendMessageRow);
});
});
},
...
$.when( myApp.getSent() ).done(function() {
// do whatever you want in here as callback
});
Inside of your getSent function create a jQuery Deferred object and return a promise. Then after request.execute has finished you can call resolve/reject. I created a small snippet to show example. Maybe you can modify to fit your needs
$(document).ready(function () {
var i =0;
function getText() {
var deferred = $.Deferred();
setTimeout(function () {
i++;
deferred.resolve("text " + i);
}, 3000);
return deferred.promise();
}
getText().then(function (value) {
console.log(value);
}).then(function () {
getText().then(function (value2) {
console.log(value2);
});
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
I am trying to have the function GetUsername to call the actual implementation and return the username if it is found back into the variable result. I am using Jasmine's done function but the test is not correct. It keeps on passing even if the expected and actual value are not the same. Any help or suggestion would be great! Thanks in advance.
Here are my objects:
var Git = {
VerifyGitUser: function (username, callback) {
$.ajax({
url: 'https://api.github.com/users/' + username
})
.done(function (data) {
callback.call(this, data);
})
.fail(function (data) {
callback.call(this, data);
})
}
}
var User = {
GetUsername: function (username) {
Git.VerifyGitUser(username, function (data) {
if (data.login) {
return data.login;
} else {
return null;
}
});
}
}
Here is my test:
describe('User', function () {
it('should return username', function (done) {
spyOn(Git, 'VerifyGitUser');
spyOn(User, 'GetUsername').and.callThrough();
var result = User.GetUsername('test');
done();
expect(Git.VerifyGitUser).toHaveBeenCalledWith('test');
expect(User.GetUsername).toHaveBeenCalled();
expect(result).toEqual('test');
})
});
There is currently no way to retrieve the data from User.GetUsername whenever it completes, so it will just return undefined. Plus your call to done() is completing the test before you assert anything with expect.
You can have User.GetUsername take a callback, just like you are doing for Git.VerifyGitUser:
var User = {
GetUsername: function (username, callback) {
Git.VerifyGitUser(username, function (data) {
if (data.login) {
callback(data.login);
} else {
callback(null);
}
});
}
}
Now you know when User.GetUserName has completed. So in your test, you can pass User.GetUserName a callback which can call done():
describe('User', function () {
it('should return username', function (done) {
spyOn(Git, 'VerifyGitUser');
spyOn(User, 'GetUsername').and.callThrough();
User.GetUsername('test', function(result) {
expect(Git.VerifyGitUser).toHaveBeenCalledWith('test');
expect(User.GetUsername).toHaveBeenCalled();
expect(result).toEqual('test');
done();
});
})
});
Other thoughts:
Do you need to call the actual API during this test? You can look into returning mock API data from VerifyGitUser using Jasmine spies.
In VerifyGitUser I don't see why you need to force context using callback.call(this, data). It seems like callback(data) is enough.
You may want to look into returning promises from async functions, instead of having them take in callbacks.
I have an Angular service that makes a call to the server and fetch the user list. The service returns a Promise.
Problem
Promise is not being resolved until and unless I call $rootScope.$digest(); either in the service or in the test itself.
setTimeout(function () {
rootScope.$digest();
}, 5000);
Apparently, Calling $rootScope.$digest(); is a workaround and I cannot call it in the angular service so I am calling it in the unit test with an interval of 5 seconds which I think is a bad practice.
Request
Please suggest an actual solution for this.
Given below is the test that I have written.
// Before each test set our injected Users factory (_Users_) to our local Users variable
beforeEach(inject(function (_Users_, $rootScope) {
Users = _Users_;
rootScope = $rootScope;
}));
/// test getUserAsync function
describe('getting user list async', function () {
// A simple test to verify the method getUserAsync exists
it('should exist', function () {
expect(Users.getUserAsync).toBeDefined();
});
// A test to verify that calling getUserAsync() returns the array of users we hard-coded above
it('should return a list of users async', function (done) {
Users.getUserAsync().then(function (data) {
expect(data).toEqual(userList);
done();
}, function (error) {
expect(error).toEqual(null);
console.log(error.statusText);
done();
});
///WORK AROUND
setTimeout(function () {
rootScope.$digest();
}, 5000);
});
})
service
Users.getUserAsync = function () {
var defered = $q.defer();
$http({
method: 'GET',
url: baseUrl + '/users'
}).then(function (response) {
defered.resolve(response);
}, function (response) {
defered.reject(response);
});
return defered.promise;
}
You can cause the promises to flush with a call to $timeout.flush(). It makes your tests a little bit more synchronous.
Here's an example:
it('should return a list of users async', function (done) {
Users.getUserAsync().then(function (data) {
expect(data).toEqual(userList);
done();
}, function (error) {
expect(error).toEqual(null);
console.log(error.statusText);
done();
});
$timeout.flush();
});
Aside: the failback won't be handled so it adds additional complexity to the test.
I have this function in my code :
let request = require("request");
let getDrillDownData = function (userId, query, callback) {
query.id = userId;
let urlQuery = buildUrlFromQuery(query);
request.get({
url: urlQuery,
json: true
}, function (error, response, data) {
if (!error && response.statusCode === 200) {
return callback(null, calculateExtraData(data));
} else if (error) {
return callback(error, null);
}
});
};
and I wish to write some unit test which verify that when the function is called with correct parameters, it is running OK,
and if there is an error, it did return the error
I wrote this unit test code :
describe.only('Server Service Unit Test', function(){
var sinon = require('sinon'),
rewire = require('rewire');
var reportService;
var reportData = require('./reportData.json');
beforeEach(function(){
reportService = rewire('../../services/reports.server.service');
});
describe('report methods', function(){
var reportData;
var query = { id: "test"};
var userId = 'testuser';
var getDrillDownData;
var request;
beforeEach(function(){
getDrillDownData = reportService.__get__('getDrillDownData');
});
it ('should get drill down data by userId and query', function(done){
var getCallback = sinon.stub();
request = {
get: sinon.stub().withArgs({
url: query,
json: true
}, getCallback.withArgs("error", {statusCode: 200}, reportData))
};
reportService.__set__('request', request);
getDrillDownData(userId, query, function(err, returnData){
(err === null).should.eql(true);
//(getCallback.withArgs(undefined, {statusCode: 200}, reportData).calledOnce).equal(true);
done();
});
});
});
But I keep getting this error:
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.
Can someone help?
Thanks
I would stub request.get() directly:
describe('report methods', function() {
// Make `request.get()` a Sinon stub.
beforeEach(function() {
sinon.stub(request, 'get');
});
// Restore the original function.
afterEach(function() {
request.get.restore();
});
it ('should get drill down data by userId and query', function(done) {
// See text.
request.get.yields(null, { statusCode : 200 }, { foo : 'bar' });
// Call your function.
getDrillDownData('userId', {}, function(err, data) {
...your test cases here...
done();
});
});
});
Using request.get.yields() (which calls the first function argument that Sinon can find in the argument list; in this case, it's the (error, response, data) callback that gets passed to request.get() in your function) you can tell Sinon which arguments to use to call the callback.
That way, you can check if the callback to request.get() handles all arguments properly.
You can use .withArgs() too (request.get.withArgs(...).yields(...)), although you have to be sure that you're using it correctly; otherwise, if the exact arguments don't match, Sinon will call the original request.get() instead of using the stubbed version.
Instead, I prefer using stub.calledWith() to check for the correct arguments after the call has been made. That integrates much better with Mocha as well, as you can use explicit assertions.