I currently am writing functional tests in Intern and have run across a small issue.
During the before portion of my test suite, I make an ajax call to an API to retrieve a variable's value. This variable being set is critical to the next step in the functional test, and therefore I want to halt the test until the variable is returned from the ajax call.
I read about Leadfoot's pollUntil() function and it sounded like it did what I needed to do. I wrote the following code:
var valueThatChanges = 0;
// ... (some functional test setup stuff)
//Ajax call that sets value of valueThatChanges
.then(function() {
return ajaxCall(valueThatChanges);
})
//valueThatChanges is initially 0 before/during ajax call
//Is set to a randomly generated value that is non-zero after response recieved
.then(pollUntil(function(valueThatChanges) {
return valueThatChanges !== 0 ? true : null;
},[valueThatChanges], 30000, 100))
.then(function() { //On success
console.log('Value is no longer zero.')
}, function(error) { //On failure/timeout
console.log(error)
})
});
However this does not work as the function enters the success callback instantly despite the value of valueThatChanges still being 0.
I understand that pollUntil() may not designed to handle situations like this (since I am not directly dealing with DOM elements in the pollUntil), but I am not sure why it does not work for this specific scenario.
It seems as though pollUntil() is not passing the updated variable on each call of it's polling function.
Can pollUntil() handle triggering an event on a change of variable value?
The general use case for pollUntil is a situation where you need to wait for something to happen in the remote browser. For example, pollUntil is often used to wait for a functional test page to fully initialize:
// ---------------------
// functional test (in Node.js)
this.remote.get('testpage.html')
.then(pollUntil('return window.pageIsReady ? true : null'))
// rest of test
// ---------------------
// remote test page (in the browser)
<script>
var pageIsReady = false;
require( ..., function ( ... ) {
// do setup stuff
pageIsReady = true;
});
</script>
If you're doing some bit of async stuff in your test setup that doesn't involve the browser, return a Promise from the before function in your test suite that will resolve when the async action is complete.
var valueThatChanges;
registerSuite({
before: function () {
return new Promise(function (resolve) {
// Assuming ajaxCall calls a callback when it's finished:
ajaxCall(function (newValue) {
valueThatChanges = newValue;
resolve();
});
});
},
test1: function () {
return this.remote
// rest of test
},
// ...
});
Related
I have a service making two consecutive calls to an API asynchronously.
I would like the app to wait for both to be resolved before proceeding and since one of calls may or may not be made, I believe $watch is the way to go versus nested or chained callbacks.
var response_complete = {call1:false, call2:false};
$http.post("myapi.com/slug", data, header).then(function(res){
/* ... */
response_complete.call1 = true;
});
if(make_this_call==true){
$http.post("myapi.com/anotherslug", data, header).then(function(res){
/*...*/
response_complete.call2 = true;
});
} else response_complete.call2 = true;
$scope.$watch("response_complete",function(){
if(response_complete.call1==true && response_complete.call2==true){
console.log("DONE!");
}
});
So the idea is to create a global variable, and watch it as the two calls complete. The second call, which is conditional, immediately sets it's response variable to true if it is not being made.
But the $watch callback is only fired once and the condition within it (call1 & call2 == true) is never met.
your watch do not work as response complete is not a $scope variable | property:
// replace this with $scope property declaration
//var response_complete = {call1:false, call2:false};
$scope.response_complete = {call1:false, call2:false};
then in your succeeding code use $scope.response_complete to modify its value and so your $watch will be triggered as $scope.response_complete changed.
A better solution:
As others have specified it is better to use $broadcast than $watch, so instead watching the variable throw events instead and catch those event inside your $scope.
$http.post("myapi.com/slug", data, header).then(function() {
// stuff
$scope.$broadcast("POST_SLUG_COMPLETE");
});
$http.post("myapi.com/anotherslug", data, header).then(function() {
// stuff
$scope.$broadcast("POST_ANOTHERSLUG_COMPLETE");
});
// then in your $scope
$scope.$on("POST_SLUG_COMPLETE", function () {
// stuff
});
$scope.$on("POST_ANOTHERSLUG_COMPLETE", function () {
// stuff
});
hope that helps
If you need your "global" variable for the current scope, you can just do:
$scope.complete = false;
$http.post("myapi.com/slug", data, header).then(function(res) {
$http.post("myapi.com/anotherslug", data, header).then(function(res) {
$scope.complete = true;
console.log("DONE!");
});
});
You may also use $rootScope for a more "global" value. Other alternatives are $broadcast or a property inside a service.
But more important is to ensure how are you using the async calls. If you want both to be resolved put the second call inside the first. The sample provided by you wouldn't work because response_complete.call1 = true is inside an async thread and it is always false by the time you try to verify it
I'm having some problems understading how the callbacks work.
I'm writing a function that has to validate the user's input.
Inside the function I have to make an HTTP GET call to my API to perform a check based on the user input.
The problem is that the validate function is called from the process function and submit function is called before the HTTP call that I make inside validate().
I cannot edit process function because it is a function used by other components.
form.process = function(){
// do stuffs
validate();
submit();
}
form.validate = function () {
// lots of checks regarding the model
...
// HTTP GET call
}
Is it possible to let the submit function waits until the HTTP GET call inside validate() ends?
Thanks in advance :)
You MUST modify validate to return a promise like this:
form.validate = function () {
var deferred = $q.defer();
// lots of checks regarding the model
...
// In http GET call:
// If success
deferred.resolve(<any value>);
// If errors
deferred.reject(<any value>);
// and now return the promise
return deferred.promise;
}
Now you CAN do anything you want in process function like this:
form.process = function(){
// do stuffs
validate().then(function(response){
submit();
}, function(reject){
// do something like showing error.
});
}
If you have more components that use this function, you MUST edit all like this.
Anyway, this is the best way to implement other GET calls in each "validate" function of your components.
function test() {
/*some business logic*/
return response;
}
function calculate() {
if (test() == true) {
console.log("Success");
} else {
console.log("Fail");
}
}
my test function is in different js file which does some business processing (processing takes some time) on data and return boolean response.
I am calling test function from calculate function (in different js file).
I am expecting console output as 'success'(test function always return true), but it is giving me 'Fail'. But if I debug this code and wait sometimes on '(if(test()==true))' then i get expected output . Basically it is a synchronization issue. How can i solve that?
I try to modify your code little bit to cater for your need which make use of JQuery Deferred object.
If you comment out "deferred.reject()", you should get the case when your function is considered as failed.
It would make your function waiting for another function to give a response.
var test = function(){
var deferred = $.Deferred();
setTimeout(function(){
deferred.resolve(); //deferred.reject();
},3000);
return deferred.promise();
};
test().done(function(){
console.log("success");})
.fail(function(){
console.log("fail");})
I think from the codes above, you would not have any problems to get "Success" route run. It really depends on how does your business logic run. If they are running in asynchronize manner then you should probably using event driven model to run your calculation logic. For example, you register an event via document.addEventListener('testCompleted', caculate()) and then you fire this event in test().
Hope it helps.
Update
A bit of context into some quirks of the illustrative code below. StoreProxy exists as a model, created by the ApplicationRouter, that has a reference to the store. This lets other objects access the store directly (for singletons, tests, etc). Example:
MyApp.StoreProxy = DS.Model.extend();
MyApp.ApplicationRoute = U.Route.extend({
model: function () {
return this.store.createRecord('storeProxy');
}
});
Before the route is executed, StoreProxy doesn't have a store property. After, it does. I can only assume this is because of some ember-data magic.
I very well realize your reaction to this may be "Ugh! No! You're doing it wrong!". Noted. We'll move to do it the right way from here over time. That said, this is where the code is now. So, given that, and given this method for getting a reference to the current store, why doesn't the code below call its accept or rejection handlers?
Original question
I'm writing a qUnit unit test for ember. I'm using fixture data. The findAll call on the store isn't resolving or rejecting the promise.
test('Find all in store', function() {
expect(1);
var findPromise;
findPromise = MyApp.StoreProxy.store.findAll('rule');
findPromise.then(function(result) {
console.log('yes');
ok(true);
}, function(error) {
console.log('no');
});
});
I tried using async tests mentioned in this question:
testing ember fixture data with quint but the resolve and reject are never called, so the test hangs indefinitely.
I've also tried placing Ember.run calls around my code, in case it's a weird run loop thing. But to no avail.
asyncTest('Find all in store', 1, function() {
var findPromise;
Ember.run(function() {
findPromise = MyApp.StoreProxy.store.findAll('rule');
findPromise.then(function(result) {
console.log('yes');
ok(true);
start();
}, function(error) {
console.log('no');
start();
});
});
});
The code I'm testing runs fine when I run the application normally (fixture adapter or no), so it feels like something with the test environment.
Any thoughts on what to try? I'm stumped.
The way that you're writing your asynchronous tests is incorrect. Check out QUnit's page on async testing. Your test should look something like this:
asyncTest('Find all in store', function() {
var findPromise = ...;
findPromise.then(function(result) {
start();
ok(result);
}, function() {
start();
ok(false);
});
});
Specifically:
You put an extra parameter in the asyncTest function, which likely causes the test to not run at all.
You're using Ember.Application.store, which is not how you should access your store (and probably isn't even a valid store). I'm not sure what your context is, but you should be getting your store from elsewhere.
You're putting the start() calls after your assertions when they should be before.
I'm using the HTML5 Web Database API and I have a function that checks to see if the app needs to go and perform it's setup phase :
this.setupRequired = function() {
var status;
try {
this.db.transaction(function(tx) {
tx.executeSql("SELECT * FROM settings", [], function (tx,result) {
if (result.rows.length == 0) {
status = true;
} else {
status = false;
}
}, function(tx) {
status = true;
})
});
} catch (e) {
return true;
}
return status
}
I'd like to return true or false based on whether there is data in the settings table or not (or if the settings table doesn't exist). The status var isn't getting set, I'm guessing this is due to scope issues and the anonymous callback functions. I'm pretty sure I need to use a closure here to correct the issue but can't quite get it right.
I'd like to return true or false based on whether there is data in the settings table or not
You can't. It is not known whether there is data in the settings table at the time the setupRequired() method has to return. This will only be known when the SQL database has performed the query and invoked the callback function. That happens only after setupRequired() and the functions that led to it being called have all exited, returning control to the browser.
That's the whole point of the callback function being passed to executeSql(). The function is not executed right away, so status will not have been touched by the time return status is reached. What's more, any exceptions that occur inside the callback functions will not cause the catch(e) block to be executed, because the try...catch block will long have been exited by the time the function that was defined inside it is actually called. So this try...catch is effectively useless.
This is ‘asynchronous’ coding. Just because some code is below a function, doesn't mean the function's going to execute first.
You could probably set status as a property of the object or another object of your own making.
You could also check at each stage what 'this' is or what value status has using firebug.
Hmm, I don't have any experience using this API but it appears that you are trying to set a value in a callback function and then return that value from the outer function. Assuming the callback is asynchronous, this will not work. I think your best bet is to change your code to something like:
this.setupRequired = function() {
try {
this.db.transaction(function(tx) {
tx.executeSql("SELECT * FROM settings", [], function (tx,result) {
if (result.rows.length == 0) {
doYourSetupFunction();
} else {
//don't;
}
}, function(tx) {
doYourSetupFunction();
})
});
} catch (e) {
doYourSetupFunction();
}
}
So that the setupRequired function is reponsible for checking and triggering the setup.