I am trying to implement a jasmine test on a simple promise implementation (asynchronous code) with the done() function and my test fails although the code being tested works perfectly fine.
Can anyone please help me to figure out what is missing in my test?
var Test = (function () {
function Test(fn) {
this.tool = null;
fn(this.resolve.bind(this));
}
Test.prototype.then = function (cb) {
this.callback = cb;
};
Test.prototype.resolve = function (value) {
var me = this;
setTimeout(function () {
me.callback(value);
}, 5000);
};
return Test;
})();
describe("setTimeout", function () {
var test, newValue = false,
originalTimeout;
beforeEach(function (done) {
originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
test = new Test(function (cb) {
setTimeout(function () {
cb();
}, 5000);
});
test.then(function () {
newValue = true;
console.log(1, newValue);
done();
});
});
it("Should be true", function (done) {
expect(1).toBe(1);
expect(newValue).toBeTruthy();
});
afterEach(function () {
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
});
});
the same test in jsfiddle: http://jsfiddle.net/ravitb/zsachqpg/
This code is testing a simple promise like object, so will call the Test object a promise for convenience.
There are two different async events after the promise creation:
1. The call to the .then() method
2. The resolving of the promise by calling the cb() function in the beforeEach() function.
In the real world these two can be called in any order and at any time.
For the test, the .then() call must be moved to the it() section's callback and all spec methods (e.g expect()) need to be called in it's callback or they'll run before it's resolved. The beforeEach() is part of the test setup while the it() function is the spec, the test itself.
The done() method needs to be called twice,
When the beforeEach() async action is finished (i.e after the cb() is called), that will start running the spec. So it should look something like this:
beforeEach(function (done) {
test = new Test(function (cb) {
setTimeout(function () {
console.log("in beforeEach() | setTimeout()");
cb(resolvedValue);
done()
}, 500);
});
});
When the spec's (it() section's) async action is finished inside the .then() method after all calls to jasmine test methods, this will tell Jasmine the spec finished running (and so the time-out won't be reached). So:
it("Should be " + resolvedValue, function (done) {
test.then(function (value) {
console.log("in then()");
expect(value).toBe(resolvedValue);
done();
});
});
Also, as you can see instead of testing that a variable's value has changed I'm testing that the value passed to the .then() method is the same as the one passed to the promise resolve cb() function as that is the right behaviour you are expecting.
Here's an updated version of your fiddle.
You can check in the browser's console to see that all callbacks are being called
Note: Changing Jasmine's DEFAULT_TIMEOUT_INTERVAL just makes it more convoluted for no reason, so I removed it and some extraneous code.
Related
I'm writing my first piece of code using Promises and am getting some unexpected results. I had some code that looked like this (using jQuery):
$('.loading-spinner').show();
$('.elements').replaceWith(function() {
// Blocking code to generate and return a replacement element
});
$('.newElements').blockingFunction();
$('.loading-spinner').hide();
To prevent the page getting blocked when this code it run, I tried using setTimeout and Promises to make it asyncronous, like this:
$('.loading-spinner').show();
var promises = [];
var promises2 = [];
$('.elements').each(function(i, el){
promises[i] = new Promise(function(resolve, reject) {
setTimeout(function() {
$(el).replaceWith(function() {
// Code to generate and return a replacement element
});
resolve(true);
}, 100);
});
});
Promise.all(promises).then(function(values) {
$('.newElements').each(function(i, el) {
promises2[i] = new Promise(function(resolve, reject) {
setTimeout(function() {
$(el).blockingFunction();
resolve(true);
}, 100);
});
});
});
Promise.all(promises2).then(function(values) {
$('.loading-spinner').hide();
});
What I'm trying to achieve is that once the Promises in promises are resolved, the Promises in promises2 are instantiated. Once these are resolved, the loading spinner is hidden.
The effect I'm getting is that, while the page isn't blocked for as long, The spinner disappears as soon as the all the Promises are set up, not waiting until they're resolved.
I can see that the the promises2 Promises dont resolve until everything in promises is resolved, so I dont understand why this is happening. I guess this is down to either me not understanding Promises properly, or not understating making code asynchronous.
You're calling Promise.all on promises2 before you populate it, in fact when you call it it contains an empty array so it calls Promise.all on an empty array and thus it resolves immediately without waiting for the promises in promises.
Quick fix:
function delay(ms){ // quick promisified delay function
return new Promise(function(r){ setTimeout(r,ms);});
}
var promises = $('.elements').map(function(i, el){
return delay(100).then(function(){
$(el).replaceWith(function() {
// Code to generate and return a replacement element
});
});
Promises.all(promises).then(function(els){
var ps = $('.newElements').map(function(i, el) {
return delay(100).then(function(){
$(el).blockingFunction();
});
});
return Promise.all(ps);
}).then(function(){
$('.loading-spinner').hide();
});
We can do better though, there is no reason to fire n timeouts for n elements:
delay(100).then(function(){
$(".elements").each(function(i,el){
$(el).replaceWith(function(){ /* code to generate element */});
});
}).
then(function(){ return delay(100); }).
then(function(){
$('.newElements').each(function(i, el) { $(el).blockingFunction(); });
}).then(function(){
$('.loading-spinner').hide();
}).catch(function(err){
throw err;
});
This Meteor server code tries to use Meteor.userId() in public method "sendEmail", but sometimes I get the error
Error Meteor.userId can only be invoked in method calls. Use this.userId
lib = (function () {
return Object.freeze({
'sendEmail': function(msg){
let userId = Meteor.userId();
//do stuff for this user
},
'otherPublicMethod': function(){
//do other things then use sendEmail
lib.sendEmail(); // <---- Error Meteor.userId can only be invoked in method calls. Use this.userId
}
});
}());
// Now I call sendEmail from any where, or can I?
Meteor.methods({
'sendEmail': (msg) => {
lib.sendEmail(msg); // <---- NO error when this is called
},
});
How can it be fixed? thx
i'm going to gently suggest you replace your use of IIFE. instead, you can take advantage of ES16 modules to define your common functions.
as you've indicated, Meteor.userId() is available in method calls, but won't be available in standalone functions on the server. the pattern i use, when invoking such functions from a method call, is to pass in the userId (or actual user). e.g.
imports/api/email/server/utils/emailUtils.js:
const SendEmail = function(userId, msg) {
// do stuff
};
export {SendEmail};
imports/api/email/server/emailMethods.js:
import {SendEmail} from '/imports/api/email/server/utils/emailUtils';
Meteor.methods({
'sendEmail': (msg) => {
check(msg, String);
// other security checks, like user authorization for sending email
SendEmail(Meteor.userId(), msg);
},
});
now, you have a re-usable SendEmail function you can call from any method or publish. additionally, by following this pattern, you're one step closer to creating testable code. i.e. it's easier to test a function into which you're injecting a userId than it is to mock "this.userId" or "Meteor.userId()".
If lib.sendEmail is being called from any async method, then ensure that you bind Meteor environment
e.g. check code below which simulates the async behaviour
lib = (function () {
return Object.freeze({
'sendEmail': function (msg) {
let userId = Meteor.userId();
console.log(userId);
//do stuff for this user
},
'otherPublicMethod': function () {
//do other things then use sendEmail
lib.sendEmail(); // <---- Error Meteor.userId can only be invoked in method calls. Use this.userId
}
});
}());
// Now I call sendEmail from any where, or can I?
Meteor.methods({
'sendEmail': (msg) => {
//simulate async behaviour + bind environment
Meteor.setTimeout(Meteor.bindEnvironment(function () {
lib.sendEmail(msg); // <---- NO error when this is called
}));
//output :
// null - if user has not logged in else
// actual userId - if user is loggedin
//simulate async behaviour without binding environment
Meteor.setTimeout(function () {
lib.sendEmail(msg); // <---- error when this is called
});
//output :
// Exception in setTimeout callback: Error: Meteor.userId can only be invoked in method calls. Use this.userId in publish functions.
},
});
I need to call two functions within grunt.registerTask, but the second function has to be called after the first function is done.
So I was wondering if we can use callbacks or promises or other asynchronous mechanisms within grunt.registerTask.
(More specifically, I need to launch karma in my first function call, and run karma in the second function call (to execute the initial unit tests). But in order to run karma, I need to launch it first. And that's what I'm missing.)
I had this:
grunt.registerTask("name_of_task", ["task_a", "task_b", "task_c"]);
And "task_b" had to be executed after "task_a" was done. The problem is that "task_a" is asynchronous and returns right away, so I needed a way to give "task_a" a few seconds to execute.
The solution:
grunt.registerTask("name_of_task", ["task_a", "task_b:proxy", "task_c"]);
grunt.registerTask("task_b:proxy", "task_b description", function () {
var done = this.async();
setTimeout(function () {
grunt.task.run("task_b");
done();
}, 2000);
});
};
From http://gruntjs.com/creating-tasks:
Tasks can be asynchronous.
grunt.registerTask('asyncfoo', 'My "asyncfoo" task.', function() {
// Force task into async mode and grab a handle to the "done" function.
var done = this.async();
// Run some sync stuff.
grunt.log.writeln('Processing task...');
// And some async stuff.
setTimeout(function() {
grunt.log.writeln('All done!');
done();
}, 1000);
});
For the sake of simplicity... having this Grunfile.js
grunt.registerTask('a', function () {
let done = this.async();
setTimeout(function () {
console.log("a");
done();
}, 3000);
});
grunt.registerTask('b', function () {
let done = this.async();
console.log("b1");
setTimeout(function () {
console.log("b2");
done();
}, 3000);
console.log("b3");
});
grunt.registerTask('c', function () {
console.log("c");
});
grunt.registerTask("run", ["a", "b", "c"]);
and then running run task, will produce the following output
Running "a" task
a
Running "b" task
b1
b3
b2 <-- take a look here
Running "c" task
c
Done.
The command is executed in this order
wait 3000 ms
console.log("a")
console.log("b1")
console.log("b3")
wait 3000 ms
console.log("b2")
console.log("c")
i have followed this tutorial from Codelab and yeoman. When implemented right you are using local storage to store the TodoList. I have problems with setting up with my tests, to test if this works. This is what i've got so far:
'use strict';
describe('Controller: MainCtrl', function () {
// load the controller's module
beforeEach(module('yeoTodoApp'), module('LocalStorageModule'));
var MainCtrl,
scope;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope, $httpBackend) {
scope = $rootScope.$new();
MainCtrl = $controller('MainCtrl', {
$scope: scope
});
}));
it('should add items to the list', function () {
var beforeLength = scope.todos.length;
scope.todo = 'Test 1';
scope.addTodo();
var afterLength = scope.todos.length;
expect(afterLength-beforeLength).toBe(1);
});
it('should add items to the list then remove', function () {
var beforeLength = scope.todos.length;
scope.todo = 'Test 1';
scope.addTodo();
scope.removeTodo(0);
var afterLength = scope.todos.length;
expect(afterLength-beforeLength).toBe(0);
});
});
The error i get is
line 12 col 68 '$httpBackend' is defined but never used.
});
How would i write my unit tests to sit the local storage?
I think at the moment the idea is kind of mocking your local storage:
Write unit tests
For an extra challenge, revisit unit testing in Step 8 and consider
how you might update your tests now that the code is using local
storage.
Tip: It's not a straight forward answer and involves knowing about
mock services. Check out Unit Testing Best Practices in AngularJS,
specifically the Mocking Services and Modules in AngularJS section.
Things may have changed since this question was asked. Anyhow, here is my solution:
'use strict';
describe('Controller: MainCtrl', function () {
// load the controller's module
beforeEach(module('mytodoyoappApp'));
var MainCtrl,
scope,
localStorage, store;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
MainCtrl = $controller('MainCtrl', {
$scope:scope
// place here mocked dependencies
});
/*mock the localStorageService*/
store={};
localStorage = {
set: function(key, value) {
store[key] = value;
},
get: function(key) {
return store[key];
}
};
}));
it('should check the list length', function () {
expect(MainCtrl.todos.length).toBe(0);
});
it('should add items to the list', function () {
MainCtrl.todoadded = 'Test 1';
MainCtrl.addTodo();
expect(MainCtrl.todos.length).toBe(1);
});
it('should add then remove an item from the list', function () {
MainCtrl.todoadded = 'Test 2';
MainCtrl.addTodo();
MainCtrl.removeTodo(0);
expect(MainCtrl.todos.length).toBe(0);
});
it('should check that the localstorage is undefined before being set', function() {
var a=localStorage.get('todos');
expect(a).toBeUndefined();
});
it('should set and get the localstorage', function() {
localStorage.set('todos', ['Test 3']);
var a=localStorage.get('todos');
expect(a).toEqual(['Test 3']);
localStorage.set('todos', ['Test 4']);
var b=localStorage.get('todos');
expect(b).toEqual(['Test 4']);
});
});
your setup is correct now (after you removed $httpBackend from the arguments list)
Controller: MainCtrl should add items to the list then remove FAILED
this error is a simple test error, which means that your code somewhere doesnt work as expected (your second test fails)
i for myself would check todos length, and not the result of a mathematical operation.
i would write your tests the test like this:
it('should add items to the list then remove', function () {
scope.todo = 'Test 1';
expect(scope.todos.length).toBe(0);
scope.addTodo();
expect(scope.todos.length).toBe(1);
scope.removeTodo(0);
expect(scope.todos.length).toBe(0);
});
you use jasmine as a test-tool. jasmine logs on errors exactly which expectation fails, so you should get something like
expect '1' to be '0'
go from there!
I'm using balanced-payments and their version 1.1 of balanced.js within Meteor.
I'm trying to create a new customer using
balanced.marketplace.customers.create(formData);
Here is my CheckFormSubmitEvents.js file
Template.CheckFormSubmit.events({
'submit form': function (e, tmpl) {
e.preventDefault();
var recurringStatus = $(e.target).find('[name=is_recurring]').is(':checked');
var checkForm = {
name: $(e.target).find('[name=name]').val(),
account_number: $(e.target).find('[name=account_number]').val(),
routing_number: $(e.target).find('[name=routing_number]').val(),
recurring: { is_recurring: recurringStatus },
created_at: new Date
}
checkForm._id = Donations.insert(checkForm);
Meteor.call("balancedCardCreate", checkForm, function(error, result) {
console.log(result);
// Successful tokenization
if(result.status_code === 201 && result.href) {
// Send to your backend
jQuery.post(responseTarget, {
uri: result.href
}, function(r) {
// Check your backend result
if(r.status === 201) {
// Your successful logic here from backend
} else {
// Your failure logic here from backend
}
});
} else {
// Failed to tokenize, your error logic here
}
// Debuging, just displays the tokenization result in a pretty div
$('#response .panel-body pre').html(JSON.stringify(result, false, 4));
$('#response').slideDown(300);
});
}
});
Here is my Methods.js file
var wrappedDelayedFunction = Async.wrap(balanced.marketplace.customers.create);
Meteor.methods({
balancedCardCreate: function (formData) {
console.log(formData);
var response = wrappedDelayedFunction(formData);
console.log(response);
return response;
}
});
I get nothing back when I submit the form, except that on the server console I do see the log of the form data.
I'm sure I'm not calling some of these async functions correctly. The hard part for me here is that the balanced function are async, but I don't know if they fit into the same mold as some of the examples I've seen.
I've tried to follow this example code.
http://meteorhacks.com/improved-async-utilities-in-meteor-npm.html
Is there a specific change that needs to be done in regard to working with balanced here? Does anyone have any tips for working with Async functions or see something specific about my code that I've done wrong?
Thanks
The NPM utilities Async.wrap does the same thing as the undocumented Meteor function Meteor._wrapAsync, in that it takes an asynchronous function with the last argument function(err, result) {} and turns it into a synchronous function which takes the same arguments, but either returns a result or throws an error instead of using the callback. The function yields in a Fiber until the asynchronous callback returns, so that other code in the event loop can run.
One pitfall with this is that you need to make sure that the function you wrap is called with the correct context. So if balanced.marketplace.customers.create is a prototype method that expects this to be set to something, it will not be set properly unless you bind it yourself, using function.bind or any of the other various library polyfills.
For more information, see https://stackoverflow.com/a/21542356/586086.
What I ended up doing was using a future. This works great, I just need to do better at catching errors. Which will be a question for a pro I think ; - )
Credit should go to user3374348 for answering another similar question of mine, which solved both of these.
https://stackoverflow.com/a/23777507/582309
var Future = Npm.require("fibers/future");
function extractFromPromise(promise) {
var fut = new Future();
promise.then(function (result) {
fut["return"](result);
}, function (error) {
fut["throw"](error);
});
return fut.wait();
}
Meteor.methods({
createCustomer: function (data) {
balanced.configure(Meteor.settings.balancedPaymentsAPI);
var customerData = extractFromPromise(balanced.marketplace.customers.create({
'name': data.fname + " " + data.lname,
"address": {
"city": data.city,
"state": data.region,
"line1": data.address_line1,
"line2": data.address_line2,
"postal_code": data.postal_code,
},
'email': data.email_address,
'phone': data.phone_number
}));
var card = extractFromPromise(balanced.marketplace.cards.create({
'number': data.card_number,
'expiration_year': data.expiry_year,
'expiration_month': data.expiry_month,
'cvv': data.cvv
}));
var associate = extractFromPromise(card.associate_to_customer(customerData.href).debit({
"amount": data.total_amount*100,
"appears_on_statement_as": "Trash Mountain" }));
});
As Andrew mentioned, you need to set the context for the method.
Here's the way you can do that with Async.wrap
Async.wrap(balanced.marketplace.customers, "create");