Mocha: adding top level before\after hooks from within describe callback

Created on 13 Mar 2018  Â·  8Comments  Â·  Source: mochajs/mocha

Prerequisites

  • [x] Checked that your issue hasn't already been filed by cross-referencing issues with the faq label
  • [x] Checked next-gen ES issues and syntax problems by using the same environment and/or transpiler configuration without Mocha to ensure it isn't just a feature that actually isn't supported in the environment in question or a bug in your code.
  • [x] 'Smoke tested' the code to be tested by running it outside the real test suite to get a better sense of whether the problem is in the code under test, your usage of Mocha, or Mocha itself
  • [x] Ensured that there is no discrepancy between the locally and globally installed versions of Mocha. You can find them with: node node_modules/.bin/mocha --version(Local) and mocha --version(Global). We recommend avoiding the use of globally installed Mocha.

Description

Is it possible to add a top level hook from within a describe callback?

Is there a way to modify the add_top_level_hook function below to achieve my desired behaviour?

Steps to Reproduce

function add_top_level_hook() {
  after('top level after', function() {
    console.log('top level after');
  });
};

describe('some suite', function() {

  add_top_level_hook();

  after('some suite after', function() {
    console.log('some suite after');
  });

  it('some test', function() {});
});

describe('some other suite', function() {

  after('some other suite after', function() {
    console.log('some other suite after');
  });

  it('some other test', function() {});
});

Desired behaviour: [What you expect to happen]

I would like the top level after to fire after hooks in some suite and some other suite
so that the output of running the code with mocha would be:

  some suite
    ✓ some test
some suite after

  some other suite
    ✓ some other test
some other suite after
top level after

Note that if we wrap the suites above with another describe, I would like the top level after to fire after the hooks of that parent suite as well.

Actual behavior: [What actually happens]

Currently I get the following output:

  some suite
    ✓ some test
top level after
some suite after

  some other suite
    ✓ some other test
some other suite after

Reproduces how often: [What percentage of the time does it reproduce?]

Versions

  • The output of mocha --version and node node_modules/.bin/mocha --version:
    5.0.2
  • The output of node --version:
    v8.9.4

Additional Information

needs-feedback question

All 8 comments

If I understand your question correctly, all you have to do is not wrap your "top level after" in a function:

```
after('top level after', function() {
console.log('top level after');
});

describe('some suite', function() {

after('some suite after', function() {
console.log('some suite after');
});

it('some test', function() {});
});

describe('some other suite', function() {

after('some other suite after', function() {
console.log('some other suite after');
});

it('some other test', function() {});
});```

If I am not mistaken, you are looking for something like this (using after.first):

function add_top_level_hook() {
  after.first('top level after', function() {    // this hook will be the very first after hook to run
    console.log('top level after');
  });
};

describe('some suite', function() {

  add_top_level_hook();

  after('some suite after', function() {
    console.log('some suite after');
  });

  it('some test', function() {});
});

describe('some other suite', function() {

  after('some other suite after', function() {
    console.log('some other suite after');
  });

  it('some other test', function() {});
});

@patrickptm what you are suggesting indeed results in desired output, however the whole point of this issues is how to achieve this output regardless of where that code that creates the before hook is called from.

@ORESoftware after.first your solution does work for me:

  after.first('top level after', function() {
        ^

TypeError: after.first is not a function

@adrian-gierakowski thanks for the detailed issue. I understand what you are seeking, but I am not sure why you would need this feature.

Currently the test scope is determined at runtime when running the suites so this behavior cannot be achieved.

If you are trying to achieve abstraction, you can do the following:

function afterAllHook() {
  console.log('after all')
}

describe("Some Test", function() {
  it('Logging Test', function() {
    console.log('hi!');
  })
  after(function() {
    console.log('suite after')
  })
  after(afterAllHook)
})

This will log:

hi!
suite after
after all

the workaround I have come up with is to create a module like so ( example in coffeescript ):

cleanup_thunks = []

after "global cleanup", ->

  if cleanup_thunks.length > 0

    Promise.all cleanup_thunks.map ( thunk ) -> thunk()


module.exports = ( cleanup_thunk ) ->

  cleanup_thunks.push cleanup_thunk

and add it to mocha.opts:

--require coffeescript/register
--file lib/mocha_global_cleanup.coffee

Which causes the after hook to be added to root suite

Now from any test file I can do:

mocha_global_cleanup = require "../lib/mocha_global_cleanup"


descrive "some suite", ->

  mocha_global_cleanup -> some_clenup_code()


  before ->

    mocha_global_cleanup -> some_more_clenup_code()


  it "some test", ->

    mocha_global_cleanup -> can_even_add_cleanup_code_from_within_test_cases()

In case you wonder about the use case: its for cleaning up shared resources which are lazily initialised by the suitestests that need them. For example a database connection, which might be used\shared by multiple ( but not all ) tests\suites. The resource is initialised only once, and stays available until all tests are finished.

@Bamieh the problem with your suggestion is that if you have another describe section after to the one in which you call after(afterAllHook), the afterAllHook hook will be called before the tests\hooks in that section. As explained above, what I want it to execute some code after all tests\suites have finished, but I also want to be able to decide what code is executed from anywhere in the test suite.

@adrian-gierakowski your code is actually the solution to the issue not a workaround.

You can access the title and other information using this, notice that this is not intended for common use.

const teardownSteps = [];
function addToTeardown(suite, conn) {
  teardownSteps.push({title: suite.title, conn});
}

after(function() {
  teardownSteps.map(teardown => {
    // ...teardown for each connection
  })
})

describe("Some Test", function() {
  it('Logging Test', function() {
    console.log('hi!');
  })
  addToTeardown(this, 'db clear');
})

closing since I have found a satisfying solution

Was this page helpful?
0 / 5 - 0 ratings