Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the purpose of Mocha's before() function?

Mocha has several 'hooks' to run assistive functionality in a test separate from the test cases themselves (clearing databases, creating mock files, etc).

However, in the case of before() (not beforeEach(), that one I get), it seems pretty redundant.

before() runs the logic once before all of the tests in the current suite, so why do I need to even wrap it in a function?

There's no observable difference between the two following:

describe('Something', function() {
    before(doSomePreTestLogic);
    //Tests ahoy
});

and

describe('Something', function() {
    doSomePreTestLogic();
    //Tests ahoy
});

What's the point of wrapping my pre-test logic with before()?

like image 605
Madara's Ghost Avatar asked Oct 10 '14 19:10

Madara's Ghost


3 Answers

There's a couple different reasons why you might opt for the before() hook in your tests:

Semantics

This is probably the biggest one. Sometimes you may need to perform a task, such as populate some dummy data in your database, prior to running an actual test. You can certainly do that in the test itself, but that throws off your reporting if you encounter an error while pre-populating, and before having run the actual test case at all. By having a before() hook, you have a logical, semantically valid place to insert this kind of logic.

Cleaner for Async Tests

Just like mocha tests can be asynchronous, so can your before() hook logic. Going back to the pre-population example, this is handy since that means all of your async pre-test logic won't force another level of indentation on all of your actual testing logic.

Consistency with other hooks:

Mocha also provides several other hooks, namely: after(), beforeEach(), and afterEach(). You could probably ask this same question about all these other hooks as well, but if you buy into the notion that any of them have a validated existence, then before() really needs to be included to round out the API.

like image 84
jmar777 Avatar answered Oct 09 '22 23:10

jmar777


The Upshot

Put directly inside a describe callback code that will build the test suite. I'm talking about calls to it but also functions that may loop over tables or files to declare a bunch of tests (by calling it in the loop).

Put inside hooks the code that actually initialize the state that the tests depend on.

All other considerations are quite secondary.

Let me explain...

The Background

Mocha executes a test suite in two phases:

  1. It discovers what tests exist. During this phase it will immediately execute the callbacks passed to describe and record for future invocation the callbacks passed to the functions that declare tests (it and the like) and functions that declare hooks (before, beforeEach, after, etc.).

  2. It runs the tests. In this phase it will run the callbacks that it recorded earlier.

The Difference

So consider this example:

function dump () { console.log("running:", this.test.fullTitle()); }
describe("top", function () {
    before(dump);
    it("test 1", dump);
    it("test 2", dump);
    describe("level 1", function () {
        before(dump);
        it("test 1", dump);
        it("test 2", dump);
    });
});

Note that fullTitle gives the whole name of a test starting from the top level describe, going through any nested describe down to the it or hook that contains the call. Run with the spec reporter and keeping only the running: lines, you get:

running: top "before all" hook: dump
running: top test 1
running: top test 2
running: top level 1 "before all" hook: dump
running: top level 1 test 1
running: top level 1 test 2

Note the order of the hooks, and how each executes immediately before the tests declared in its respective describe callback.

Consider then this suite:

function dump () { console.log("running:", this.test.fullTitle()); }
function directDump() { console.log("running (direct):", this.fullTitle()); }
describe("top", function () {
    directDump.call(this);
    it("test 1", dump);
    it("test 2", dump);
    describe("level 1", function () {
        directDump.call(this);
        it("test 1", dump);
        it("test 2", dump);
    });
});

Run with the spec reporter and keeping only the running: lines, you get:

running (direct): top
running (direct): top level 1
running: top test 1
running: top test 2
running: top level 1 test 1
running: top level 1 test 2

Note how both calls to directDump are run before anything else.

The Consequences

  1. If any initialization code that you put directly inside a callback to describe fails, the entire run fails right away. No test will be executed. End of story.

  2. If any initialization code that you put inside a before hook fails, the consequences are contained. For one thing, because a before hook is run just at the moment is needed, there is an opportunity any tests that are scheduled earlier can run. Also, Mocha will only skip those tests that depend on the before hook. For instance, let's assume this suite:

    function dump () { console.log("running:", this.test.fullTitle()); }
    describe("top", function () {
        before(dump);
        it("test 1", dump);
        it("test 2", dump);
        describe("level 1", function () {
            before(function () { throw new Error("foo"); });
            it("test 1", dump);
            it("test 2", dump);
        });
    
        describe("level 1 (second)", function () {
            before(dump);
            it("test 1", dump);
            it("test 2", dump);
        });
    });
    

    If you run it with the spec reporter, the entire output (minus the stack trace) will be something like:

      top
    running: top "before all" hook: dump
    running: top test 1
        ✓ test 1
    running: top test 2
        ✓ test 2
        level 1
          1) "before all" hook
        level 1 (second)
    running: top level 1 (second) "before all" hook: dump
    running: top level 1 (second) test 1
          ✓ test 1
    running: top level 1 (second) test 2
          ✓ test 2
    
    
      4 passing (5ms)
      1 failing
    
      1) top level 1 "before all" hook:
         Error: foo
         [stack trace]
    

    Note how a) some tests ran before the failing hook and b) Mocha still ran the tests that do not depend on the hooks.

like image 21
Louis Avatar answered Oct 09 '22 23:10

Louis


Semantics, at both the machine and human level.

Also, it keeps test code in line with the "exports" interface, e.g.,

module.exports = {
  before: function(){
    // ...
  },

  'Array': {
    '#indexOf()': {
      'should return -1 when not present': function(){
        [1,2,3].indexOf(4).should.equal(-1);
      }
    }
  }
};
like image 1
Dave Newton Avatar answered Oct 09 '22 21:10

Dave Newton