59

This question relates to the Mocha testing framework for NodeJS.

The default behaviour seems to be to start all the tests, then process the async callbacks as they come in.

When running async tests, I would like to run each test after the async part of the one before has been called.

How can I do this?

Jawa
  • 2,336
  • 6
  • 34
  • 39
fadedbee
  • 42,671
  • 44
  • 178
  • 308

4 Answers4

38

The point is not so much that "structured code runs in the order you've structured it" (amaze!) - but rather as @chrisdew suggests, the return orders for async tests cannot be guaranteed. To restate the problem - tests that are further down the (synchronous execution) chain cannot guarantee that required conditions, set by async tests, will be ready they by the time they run.

So if you are requiring certain conditions to be set in the first tests (like a login token or similar), you have to use hooks like before() that test those conditions are set before proceeding.

Wrap the dependent tests in a block and run an async before hook on them (notice the 'done' in the before block):

var someCondition = false

// ... your Async tests setting conditions go up here...

describe('is dependent on someCondition', function(){

  // Polls `someCondition` every 1s
  var check = function(done) {
    if (someCondition) done();
    else setTimeout( function(){ check(done) }, 1000 );
  }

  before(function( done ){
    check( done );
  });

  it('should get here ONLY once someCondition is true', function(){ 
    // Only gets here once `someCondition` is satisfied
  });

})
papercowboy
  • 3,369
  • 2
  • 28
  • 32
  • 1
    How would `someCondition` ever change if the before callback is locked up in a while loop?! This is not the way JavaScript works. – natevw Jun 03 '13 at 19:50
  • The answer to your question is in the comment line 3: "..your Async tests setting conditions go up here". Specifically: Async, setting conditions, scoped outside the `describe()` which instantiate before the `before()`. Your latter assertion about Javascript not working this way is false. – papercowboy Jun 04 '13 at 22:31
  • While your code is stuck repeatedly checking !someCondition over and over again, none of your other code can run. (All other callbacks associated with events/timers will be prevented from executing.) The only way this would work is if someCondition is set true before the loop begins — otherwise it will hang. Try it. – natevw Jun 05 '13 at 05:46
  • In fact, it is true that async tests in Mocha run in the order of declaration (and that async `before` hook run before the first test, either async or synchronous). The only case when polling mechanism can be desirable is when the code that sets async conditions doesn't accept a callback and does not return a promise. – skozin Sep 21 '14 at 01:49
13

use mocha-steps

it keeps tests sequential regardless if they are async or not (i.e. your done functions still work exactly as they did). It's a direct replacement for it and instead you use step

WiR3D
  • 1,465
  • 20
  • 23
9

I'm surprised by what you wrote as I use. I use mocha with bdd style tests (describe/it), and just added some console.logs to my tests to see if your claims hold with my case, but seemingly they don't.

Here is the code fragment that I've used to see the order of "end1" and "start1". They were properly ordered.

describe('Characters start a work', function(){
    before(function(){
      sinon.stub(statusapp, 'create_message');
    });
    after(function(){
      statusapp.create_message.restore();
    });
    it('creates the events and sends out a message', function(done){
      draftwork.start_job(function(err, work){
        statusapp.create_message.callCount.should.equal(1);
        draftwork.get('events').length.should.equal(
          statusapp.module('jobs').Jobs.get(draftwork.get('job_id')).get('nbr_events')
        );
        console.log('end1');
        done();
      });
    });
    it('triggers work:start event', function(done){
      console.log('start2');
      statusapp.app.bind('work:start', function(work){
        work.id.should.equal(draftwork.id);
        statusapp.app.off('work:start');
        done();
      });

Of course, this could have happened by accident too, but I have plenty of tests, and if they would run in parallel, I would definitely have race conditions, that I don't have.

Please, refer to this issue too from the mocha issue tracker. According to it, tests are run synchronously.

Akasha
  • 2,162
  • 1
  • 29
  • 47
  • 1
    This only shows that ordered code is run in order. That doesn't happen by accident. That's "how it works". – papercowboy Oct 19 '12 at 22:55
  • 1
    This shows that ordered async tests run in order: `end1` will always occur before `start2`. To simplify the example, you can replace contents of the first test with `setTimeout(done, 1000)`. And if you replace the `before` hook with the async one, it will always run before the first test. As I understand, this is exactly the behavior that topic starter was interested in, and this behavior is the default and the only one, at least by now. – skozin Sep 21 '14 at 02:09
5

I wanted to solve this same issue with our application, but the accepted answer didn't work well for us. Especially in the someCondition would never be true.

We use promises in our application and these made it very easy to structure the tests accordingly. The key however is still to delay execution through the before hook:

var assert = require( "assert" );

describe( "Application", function() {
  var application = require( __dirname + "/../app.js" );
  var bootPromise = application.boot();

  describe( "#boot()", function() {
    it( "should start without errors", function() {
      return bootPromise;
    } );
  } );

  describe( "#shutdown()", function() {
    before( function() {
      return bootPromise;
    } );

    it( "should be able to shut down cleanly", function() {
      return application.shutdown();
    } );
  } );
} );
Community
  • 1
  • 1
Oliver Salzburg
  • 21,652
  • 20
  • 93
  • 138
  • 3
    I'd better place the second and third lines of code (`application = ...` and `bootPromise = ...`) inside the async `before` block in the top-level suite ("Application"). Otherwise, any exception thrown from this code will not be caught and reported properly and, even worse, will prevent execution of all remaining tests. – skozin Sep 21 '14 at 01:59