I'm having some trouble with getting a simple async test working. The following piece of code doesn't throw any errors in the console, even though it should, because the data passed to the function does not equal 0:
define([
'intern!bdd',
'intern/chai!expect'
], function (bdd, expect) {
with (bdd) {
describe('Test', function () {
it('async test', function(){
var dfd = this.async(2000);
var wait = function(ms) {
setTimeout(function(){
dfd.resolve('test');
}, ms);
return dfd.promise;
};
wait(1500).then(dfd.callback(function (data) {
// data === 'test', so this should fail.
expect(data).to.equal(0);
}), dfd.reject.bind(dfd));
});
});
}
});
I'm pretty sure I messed up somewhere because I never worked with promises until now, but I can't figure out where. Any ideas would help a lot. Thanks!
You’re using the same Deferred object for two different asynchronous operations and resolving it (= successful test) the first time. You need to create your own separate Deferred object for the wait function:
define([
'intern!bdd',
'intern/chai!expect',
'intern/node_modules/dojo/Deferred'
], function (bdd, expect, Deferred) {
with (bdd) {
describe('Test', function () {
it('async test', function(){
var dfd = this.async(2000);
var wait = function(ms) {
var waitDfd = new Deferred();
setTimeout(function(){
waitDfd.resolve('test');
}, ms);
return waitDfd.promise;
};
wait(1500).then(dfd.callback(function (data) {
// data === 'test', so this should fail.
expect(data).to.equal(0);
}), dfd.reject.bind(dfd));
});
});
}
});
Related
I've search for a sollution but I didn't find something like that.
I'm using angular, I want to call a function inside another function, and wait for its response.
the 2nd function is:
self.changeProvider = function() {
var contexec = false;
if (!checkIsFit()) {
contexec = true;
} else {
contexec = false;
}
if (contexec) {
var modalOptions = {
closeButtonText: $translate.instant('closeButtonText'),
actionButtonText: $translate.instant('ok'),
headerText: $translate.instant('changeProvidertitle'),
bodyTemplate: '../themes/default/src/app/shoppingCart/changeProvider/changeProvider.tpl.html',
margin: true
};
var modalDefaults = {
backdrop: 'static',
templateUrl: '../themes/default/src/app/shoppingCart/changeProvider/changeProvider.tpl.html',
controller: 'ChangeProviderCtrl',
size: 'sm',
resolve: {
modalData: function() {
return {
data: $scope.arrayToChangeProvider
};
}
}
};
modalService.showModal(modalDefaults, modalOptions)
.then(function(result) {
//some stuff
});
}
};
And the other function:
var checkIsFit = function() {
if ( $scope.cabstatus != 4 ) {
return false;
} else {
var modalOptions = {
closeButtonText: $translate.instant('closeButtonText'),
actionButtonText: $translate.instant('ok'),
headerText: $translate.instant('cabisfittedtitle'),
bodyTemplate: '../themes/default/src/app/shoppingCart/checkIsFit/checkIsFit.tpl.html',
margin: true
};
var modalDefaults = {
backdrop: 'static',
templateUrl: '../themes/default/src/app/shoppingCart/checkIsFit/checkIsFit.tpl.html',
controller: 'CheckIsFitCtrl',
size: 'sm',
resolve: {
modalData: function() {
return {
};
}
}
};
modalService.showModal(modalDefaults, modalOptions)
.then(function(result) {
if (result.msg === 'ok') {
var params = {
token: $scope.token,
fkidpedido: $scope.pendingOrderLineList[0].FK_IDPEDIDO,
userid : $scope.userid
};
shoppingCartService.postResetAgr(params, function() {
return true;
}, function() {
/*Notification.error({
message: $translate.instant('components.activity.actions.deleteActivityError')
});*/
});
return false;
} else {
return true;
}
});
}
};
The problem is the function changeProvider still executing and opens the modal first to resolve the funcion checkIsFit()
I want to wait checkIsFit is resolved and then continue with the functions of changeProvider
I cannot include the checkIsFit() functionallity inside changeProvider because I want to use checkIsFit() into another functions.
Any help will be appreciate.
Thanks in advance
I believe what you're looking for are deferred objects and promises. Check out the documentation for $q:
https://docs.angularjs.org/api/ng/service/$q
I'd recommend giving this a good read because this is a really important and powerful concept for ANY Javascript developer.
At the essence, deferred objects and promises allow you run asynchronous processes and callback to a function when a process is complete.
The modalService.showmodal method returns a promise. Create functions that return those promises.
var modalPromise1Fn = function () {
var promise1 =
modalService.showModal(modalDefaults1, modalOptions2)
.then(function(result) {
//some stuff
});
return promise1;
};
var modalPromise2Fn = function () {
var promise2 =
modalService.showModal(modalDefaults2, modalOptions2)
.then(function(result) {
//some stuff
});
return promise2;
};
This use the .then method of the first promise to chain the second promise.
var derivedPromise =
modalPromise1Fn().then( function() {
var promise2 = modalPromise2Fn();
//return to chain the second promise
return promise2;
});
From the Docs:
Chaining promises
Because calling the .then method of a promise returns a new derived promise, it is easily possible to create a chain of promises.
It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs .
-- AngularJS $q Service API Reference -- Chaining Promises
I try to test my app with Jasmine and got the following problem:
I will calculate something in the then function of my promise. That's the point where I need to test my code.
Here is the code of my controller:
TestCtrl.$inject = ["$scope", "TestService"];
/* ngInject */
function TestCtrl($scope, TestService) {
$scope.loadData = function () {
TestService.getData().then(function (response) {
$scope.data = response.data;
$scope.filtered = $scope.data.filter(function(item){
if(item.id > 1000){
return true;
}
return false;
})
});
}
}
And my Jasmine test code:
describe('TestService tests', function () {
var $q;
beforeEach(function () {
module('pilot.fw.user');
});
beforeEach(inject(function (_$q_) {
$q = _$q_;
}));
describe('UserController Tests', function () {
beforeEach(inject(function (_$httpBackend_, $rootScope, $controller) {
this.scope = $rootScope.$new();
this.$rootscope = $rootScope;
this.$httpBackend = _$httpBackend_;
this.scope = $rootScope.$new();
var TestServiceMock = {
getData: function () {
var deferred = $q.defer();
var result = [{
"id": 1720,
"user": 1132
},
{
"id": 720,
"user": 132
}, {
"id": 1721,
"user": 1132
}];
deferred.promise.data = result;
deferred.resolve(result);
return deferred.promise;
}
};
this.controller = $controller('TestCtrl', {
'$scope': this.scope,
'TestService': TestServiceMock
});
}));
it('test', function(){
this.scope.loadData();
expect(true).toBeTruthy();
})
});
});
The strange thing I don't understand is (tested with console logs):
My promise is created and returned
My loadData function is called and it will call the getData() function from the TestService
Everything inside the then function won't be executed although I return the promise as resolved
So how could I test the code inside the then function?
Thanks for help
the jasmine 'it' method takes a done parameter that you can call for async testing
it('Should be async', function(done) {
someAsyncFunction().then(function(result) {
expect(result).toBe(true);
done();
});
});
Feel free to go as deep as you want, just be sure to call done when EVERYTHING is finished. Jasmine's default timeout is 5 seconds per test, so if the async stuff isn't done by then jasmine will crash. You can change this setting in the configs or set it in the terminal.
This is straight from the jasmine docs, showing you how to handle the default timeout interval
describe("long asynchronous specs", function() {
var originalTimeout;
beforeEach(function() {
originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
});
it("takes a long time", function(done) {
setTimeout(function() {
done();
}, 9000);
});
afterEach(function() {
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
});
});
I think that if it doesn't work in 10 seconds, you may have faulty methods. ESPECIALLY if you are talking to a local server / db. This stuff should only take this long if you are performing HEAVY computations, or are hitting an external api with a not-so-great internet connection. If everything is local (or stubbed / mocked!) then anything over 5-10 seconds is a definite red flag.
You better watch this https://codecraft.tv/courses/angular/unit-testing/asynchronous/
You have actually 3 ways:
1) use regular it:
it('test', (done) => {
const spy = spyOn(func, 'bar').and.returnValue(Promise.resolve(true));
spy.calls.mostRecent().returnValue.then(res => {
...your expect here...
done();
})
} );
2) use async in beforeEach and it:
it('test', async(() => {
spyOn(func, 'bar').and.returnValue(Promise.resolve(true));
fixture.whenStable().then(res => {
...your expect here...
})
} ));
3) use fakeAsync if you don't have Http or XHR calls:
it('test', fakeAsync(() => {
spyOn(func, 'bar').and.returnValue(Promise.resolve(true));
tick();
...your expect here...
} ));
hope this solution helps. One approach I've found useful when testing is mocking dependencies. I've tried to comment out what I've done as much as possible.
var returnMock, $scope, TestServiceMock, controller;
beforeEach(module('app'));
beforeEach(inject(function($controller) {
returnMock = {
then: jasmine.createSpy(),
};
$scope = {};
// first assumption is You are testing TestService extensively,
// I don't care about what getData has to do to get results
// All I care about is it gets called when I call loadData
TestServiceMock = {
getData: jasmine.createSpy().and.returnValue(returnMock);
};
controller = $controller;
}));
it('should load data when loadData function is called and result set is
under 1000', function() {
controller('TestCtrl', {
$scope,
TestServiceMock
});
// another assumption is your data comes back in such a format
// perhaps in the actual code check whether data exists and proceed
// or do some other action
var returnedData = {
data: [
{
id: 1,
name: 'item 1',
},
]
}
// when I execute the function/method
$scope.loadData();
// I expect getData to be called
expect(TestServiceMock.getData).toHaveBeenCalled();
// I expect then to be called and the reason is I mocked it
expect(returnMock.then).toHaveBeenCalledWith(jasmine.any(Function));
returnMock.then.calls.mostRecent().args[0](returnedData);
// expect data on scope to be equal to my mocked data
expect($scope.data).toEqual(returnedData.data);
// don't expect any result because 1 < 1000
expect($scope.filtered).toEqual([]);
expect($scope.filtered.length).toEqual(0);
});
it('should load data when loadData function is called and result set is over 1000',
function() {
controller('TestCtrl', {
$scope,
TestServiceMock
});
var returnedData = {
data: [
{
id: 1,
name: 'item 1',
},
{
id: 1000,
name: 'item 1000',
},
{
id: 1001,
name: 'item 1000',
},
{
id: 1002,
name: 'item 1002',
}
]
}
$scope.loadData();
expect(TestServiceMock.getData).toHaveBeenCalled();
expect(returnMock.then).toHaveBeenCalledWith(jasmine.any(Function));
returnMock.then.calls.mostRecent().args[0](returnedData);
expect($scope.data).toEqual(returnedData.data);
// expect a result because some entries in the mocked data have id > 1000
expect($scope.filtered).toEqual([
{
id: 1001,
name: 'item 1000',
},
{
id: 1002,
name: 'item 1002',
}]);
expect($scope.filtered.length).toEqual(2);
});
Official Jasmine Docs explain most of the concepts extensively. Hope the solution helps!!!!
Let me tell ya what I do, for Angular 1.x and 2.x+ projects. Use the angular testing tools to get rid of callbacks/nests in your async tests. In angular 1.x, that means using a combination of $q and $rootScope.$apply(). In angular 2.x+, that means using something like fakeAsync.
From the Angular 1.x docs
it('should simulate promise', inject(function($q, $rootScope) {
var deferred = $q.defer();
var promise = deferred.promise;
var resolvedValue;
promise.then(function(value) { resolvedValue = value; });
expect(resolvedValue).toBeUndefined();
// Simulate resolving of promise
deferred.resolve(123);
// Note that the 'then' function does not get called synchronously.
// This is because we want the promise API to always be async, whether or not
// it got called synchronously or asynchronously.
expect(resolvedValue).toBeUndefined();
// Propagate promise resolution to 'then' functions using $apply().
$rootScope.$apply();
expect(resolvedValue).toEqual(123);
}));
The disadvantage is that your code is tied to angular, the advantages are that your code is flat and it's portable to 2.x+!
I was a fan of the mocha test runner that allowed me to return promises in my tests, you could try to get that going, but there are downsides to that as well like needing to modify your code specifically for a test.
In regards to your controller, you should 'return' values like so.
TestCtrl.$inject = ["$scope", "TestService"];
/* ngInject */
function TestCtrl($scope, TestService) {
$scope.loadData = function () {
// Return this call, since it will return a new promise
// This is what let's you do $scope.loadData.then()
return TestService.getData().then(function (response) {
// What you return in here will be the first argument
// of your then method, in the tests / any env
// Ex. return 'foo'
// will result in .then(result => result === 'foo') //=> true
// return one of these, i suggest the data, go SRP!
return $scope.data = response.data;
// I would do this stuff in a separate function, but you
// can return 'filtered' instead if you like.
//
// $scope.filtered = $scope.data.filter(function(item){
// if(item.id > 1000){
// return true;
// }
// return false;
// });
});
}
}
Remember that calling something AFTER 'then' doesn't mean anything, values must be called INSIDE 'then'. Not after it, or before it. But inside it. Like Tom Green and that poor moose in Freddy Got Fingered.
Folks,
Lets say I have the following function. What would be a proper way to write a Spy, or any other method of testing this with Jasmine?
var Ideas = require('../models/Ideas').Ideas;
var doSomething = function doSomething(req, rsp, userId) {
controllerHelper.batchGet(req, rsp,
function(ids) { return Ideas.get(ids, userId); },
function(tags) { return Ideas.getTags(tags, userId); },
function(tags) { return Ideas.getSpecificTags(tags, userId); },
function() { return Ideas.getAll(userId); });
};
Thanks!
If you want to test if the function has been called or with what arguments it was called you can use jasmine.createSpy()...
it("should test your function", function () {
doSomething = jasmine.createSpy();
doSomething(1,2,3);
expect(doSomething).toHaveBeenCalled();
expect(doSomething).toHaveBeenCalledWith(1,2,3);
});
If you want to test the return result of the function you can just call it in your expect...
it("should test your function", function () {
expect(doSomething(req, rsp, userId)).toEqual(expectedResult);
});
I have an AngularJS app where I need to test a workflow and guarantee that the correct values are set after an event is broadcasted.
In 1.3 I would do this:
it('should have the correct match workflow', function() {
// matchMaking event
runs(function() {
scope.$broadcast('matchMaking', gameId);
});
waitsFor(function() {
return (scope.match && scope.match.game);
}, 'A game should be defined', 3000);
runs(function() {
expect(scope.match.game).toBeDefined();
});
// matchCreate event
runs(function() {
scope.$broadcast('matchCreate', gameId, {}, {});
});
waitsFor(function() {
return scope.match.status === 'CREATED';
}, 'Match status should be \'CREATED\'', 3000);
runs(function() {
expect(scope.match.id).toBeDefined();
expect(scope.match.player).toBeDefined();
expect(scope.match.opponent).toBeDefined();
});
// matchPrepare event
runs(function() {
scope.$broadcast('matchPrepare');
});
waitsFor(function() {
return scope.match.status === 'PREPARED';
}, 'Match status should be \'PREPARED\'', 3000);
runs(function() {
expect(scope.match.id).toBeDefined();
});
// ... continues
});
With Jasmine 2.0, it seems that the only solution to test a workflow is to chain setTimeout functions inside each other (all expectations must be inside the same spec in order to use the same scope):
beforeEach(inject(function($rootScope, $compile) {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
scope = $rootScope;
element = angular.element('<pg-match-making></pg-match-making>');
$compile(element)($rootScope);
$rootScope.$digest();
}));
it('should have the correct match workflow', function(done) {
var timeoutTick = 100;
scope.$broadcast('matchMaking', gameId);
setTimeout(function(){
expect(scope.match.game).toBeDefined();
scope.$broadcast('matchCreate', gameId, {}, {});
setTimeout(function(){
expect(scope.match.status).toEqual('CREATED');
expect(scope.match.id).toBeDefined();
expect(scope.match.player).toBeDefined();
expect(scope.match.opponent).toBeDefined();
scope.$broadcast('matchPrepare');
setTimeout(function(){
expect(scope.match.status).toEqual('PREPARED');
expect(scope.match.id).toBeDefined();
// ... call done() on the last setTimeout()
}, timeoutTick);
}, timeoutTick);
}, 6000);
});
I ended up with a pile of 7 setTimeout which make the source code a lot harder to read and the test terribly slow to run.
Isn't there a better way to test a workflow with Jasmine 2.0?
I have a solution for your problem. I have build a small simple async test framework that works well with Jasmine 2.x, but it uses the jQuery Deferred object to schedule continuations.
function asyncWait(delay) {
return new $.Deferred(function () {
var _self = this;
setTimeout(function () {
_self.resolve();
}, delay || 0);
}).promise();
}
var Async = function(init) {
var d = new $.Deferred(init);
this.promise = d.promise();
d.resolve();
};
Async.prototype.continueWith = function (continuation, delay) {
var _self = this;
_self.promise.then(function () {
_self.promise = asyncWait(delay).then(continuation);
});
return _self;
};
Async.prototype.waitsFor = function (condition, timeout, pollInterval) {
pollInterval = pollInterval || 10;
timeout = timeout || 5000;
var _self = this,
wait_d = new $.Deferred(),
t = 0,
ln = function () {
if (condition()) {
wait_d.resolve();
return;
}
if (t >= timeout) {
wait_d.reject();
throw "timeout was reached during waitsFor";
}
t += pollInterval;
setTimeout(ln, pollInterval);
};
_self.promise.then(ln);
_self.promise = wait_d.promise();
return _self;
};
To use this code, wire up a Jasmine test and use a new instance of the Async class,
it("some async test workflow I want to run", function (done) {
new Async(function () {
//wire up the first async call here
var timeoutTick = 100;
scope.$broadcast('matchMaking', gameId);
}).continueWith(function () {
expect(scope.match.game).toBeDefined();
scope.$broadcast('matchCreate', gameId, {}, {})
}, 6000).continueWith(function () {
//more stuff here
}).waitsFor(function () {
// a latch function with timeout - maybe wait for DOM update or something
return $(".my-statefull-element").val() === "updated";
}, 1000).continueWith(done); //finish by waiting for done to be called
});
This code is not a 100% fool proof, but it works for me. Let me know if you have any issues with it.
With a little bit of extra javascript, you can make the jasmine behave similarly to what you had with 1.3.1, and you don't need to pull in any additional libraries. You just need to implement the polling function that you are missing. Here's a simplified example:
var value1 = false;
var value2 = false;
var value3 = false;
var test1 = function _test1() {
setTimeout( function() { value1 = true; }, 1000 );
}
var test2 = function _test2() {
setTimeout( function() { value2 = true; }, 5000 );
}
var test3 = function _test3() {
setTimeout( function() { value3 = true; }, 300000 );
}
var asyncCheckFn = function( done, waitFor, verify ) {
if ( waitFor() ) {
verify();
done();
} else {
console.log( 'checking...' );
setTimeout( function() { asyncCheckFn(done, waitFor, verify) }, 500);
}
};
describe('async test suite', function() {
it( 'works with short test', function( done ) {
test1();
asyncCheckFn( done, function() {
return value1;
}, function() {
expect( value1 ).toBe( true );
});
}, 3000 );
it( 'longer delay', function( done ) {
test2();
asyncCheckFn( done, function() {
return value2;
}, function() {
expect( value2 ).toBe( true );
});
}, 10000 );
it( 'fails', function( done ) {
test3();
asyncCheckFn( done, function() {
return value3;
}, function() {
expect( value3 ).toBe( true );
});
}, 3000 );
});
The asyncTestFn() performs the same task that the waitsFor() function used to do -- tests a condition until it is true. The overall timeout for the test is controlled by the last parameter passed to the it() function. Here's your example rewritten as a linear test instead of nested setTimeouts:
describe('should have the correct match workflow', function() {
var timerTick = 100;
// matchMaking event
it('defines the game', function(done) {
scope.$broadcast('matchMaking', gameId);
asyncCheckFn(done, function() {
return scope.match && scope.match.game;
}, function() {
expect(scope.match.game).toBeDefined();
});
}, 6000);
it('creates the match', function(done) {
scope.$broadcast('matchCreate', gameId, {}, {});
asyncCheckFn(done, function() {
return scope.match.status === 'CREATED';
}, function() {
expect(scope.match.id).toBeDefined();
expect(scope.match.player).toBeDefined();
expect(scope.match.opponent).toBeDefined();
});
}, timerTick);
it('prepares the match', function(done) {
scope.$broadcast('matchPrepare');
asyncCheckFn(done, function() {
return scope.match.status === 'PREPARED';
}, function() {
expect(scope.match.id).toBeDefined();
});
}, timerTick);
// ... continues
});
Hope this helps.
(I know this is a little old, but I came across the question when trying to solve a similar problem -- how to nest sequential, dependent tests (answer, you can't... ))
(samples tested with Jasmine 2.2)
I have an object
var actions = {
'photos': function()
{
var self = this; // self = actions
$.get('./data.php?get=photos', function(data)
{
self.result = data;
});
},
'videos': function()
{
var self = this;
$.get('./data.php?get=videos', function(data)
{
self.result = data;
});
}
};
Each function creates one more item in actions called result
Then, instead of switch I use this (works good):
if (actions[action])
{
actions[action](); // call a function
console.log(actions);
console.log(actions.result);
}
action is a variable with value photos or videos.
console.log(actions) gives this:
Object
message: function ()
messages: function ()
profile: function ()
profile-edit: function ()
result: "<div>...</div>"
__proto__: Object
So I think there is resultitem in actions with the value "<div>...</div>".
But, console.log(actions.result) returns undefined.
Why?
I know all this code may be rewrited, but I would like to understand the reason of undefined.
Because we are dealing with asynchronous requests, we use "callbacks".
A callback is called when an asynchronous request is ready. Your request will get a response, and you send that response with the callback. The callback handles the response.
var actions = {
'photos': function(callback)
{
$.get('./data.php?get=photos', callback);
},
'videos': function(callback)
{
$.get('./data.php?get=videos', callback);
}
};
var action = 'photos';
actions[action](function(data) {
console.log(data);
});
Since you ensist on keeping the values, I would use this structure:
var actions = {
'photos': function()
{
$.get('./data.php?get=photos', function() {
this.__callback('photos', data);
});
},
'videos': function()
{
$.get('./data.php?get=videos', function() {
this.__callback('videos', data);
});
},
'__callback': function(action, data) {
this.results[action].push(data);
},
'results': {
'photos': [],
'videos': []
}
};
var action = 'photos';
actions[action]();
// use a timeout because we are dealing with async requests
setTimeout(function() {
console.log(actions.results); // shows all results
console.log(actions.results.photos); // shows all photos results
console.log(actions.results.videos); // shows all videos results
}, 3000);
gaaah what a horrible piece of code...