what done() is for and how to use it ( protractor, jasmine)

You need to use done if your test creates a parallel TaskQueue in your test's Control Flow (read more about promises and control flow).

For example:

describe('Control Flow', function() {
    function logFromPromise(text) {
        var deferred = protractor.promise.defer();
        deferred.then(function() {
            console.log(text);
        });
        deferred.fulfill();
        return deferred;
    }

    it('multiple control flows', function() {
        setTimeout(function() {
            logFromPromise('1');
        });
        logFromPromise('0');
    });
}

Calling setTime creates a parallel Task Queue in the control:

ControlFlow
| TaskQueue
| | Task<Run fit("multiple control flows") in control flow>
| | | TaskQueue 
| | | | Task <logFromPromise('0');>
| TaskQueue
| | Task <setTimeout>

Protractor thinks the test is "done" after 0 is printed. In this example, 1 will probably be printed after the test is completed. To make protractor wait for Task <setTimeout>, you need to call the done function:

    it('multiple control flows', function(done) {
        setTimeout(function() {
            logFromPromise('1').then(function() {
                done();
            });
        });
        logFromPromise('0');
    });

If you can, let protractor handle when the test is "done". Having parallel TaskQueues can lead to unexpected race conditions in your test.


Here is a sample describe that you can run and see what happens. I have to mention that I don't use Protractor so there might exist some additional considerations to be made concerning its specific capabilities.

describe('Done functionality', function(){

    var echoInOneSecond = function(value){
        console.log('creating promise for ', value);
        return new Promise(function(resolve, reject){
            console.log('resolving with ', value);
            resolve(value);
        });
    };

    it('#1 this will untruly PASS', function(){
        var p = echoInOneSecond('value #1');
        p.then(function(value){
            console.log('#1 expecting...and value is ', value);
            expect(value).toBe('value #1');
        });
    });

    it('#2 this will NOT FAIL', function(){
        var p = echoInOneSecond('value #2');
        p.then(function(value){
            console.log('#2 expecting... and value is ', value);
            expect(value).not.toBe('value #2');
        });
    });

    it('3 = will truly FAIl', function(done){
        var p = echoInOneSecond('value #3');
        p.then(function(value){
            console.log('#3 expecting... and value is ', value);
            expect(value).not.toBe('value #3');
            done();
        });
    });

    it('4 = this will truly PASS', function(done){
        var p = echoInOneSecond('value #4');
        p.then(function(value){
            console.log('#4 expecting... and value is ', value);
            expect(value).toBe('value #4');
            done();
        });
    });
});

when running the test you will note the sequence: first promises #1, #2, #3 will be created and resolved one by one. Please note that expectation for #1 and #2 will not be run yet because promises are resolved asynchronously.

Then, since #3 test uses done, after #3 promise is created, functions for thens of all previous promises are evaluated: you will see '#1 expecting...' and '#2 expecting...', but jasmine won't care about that because tests #1 and #2 are already finished and everything concerning them done. Only after those #3 expectation is made and it will truly fail because jasmine does take care of everything that happens before done() is made.

And then you can watch #4 test normal flow -- creating promise, resolving, expectation, everything considered by jasmine so expectation will truly pass.