'Enforce one describe per file

The Story:

We have a rather big test codebase with Protractor+Jasmine tests.

One of the current problems we have is that some of the test/spec files contain more than one describe which causes troubles from time to time - for example, when debugging tests one by one (or in batches) we use fdescribe/fit; and occasionally we don't notice that there are other decribes in the same file at the bottom eventually leading to parts of the tests to be unintentionally skipped.

In other words, this is sort of a variation of "one assertion per test" type of a rule helping to keep the test codebase clean and "flat".

The Question:

Is there a way to prohibit having more than one describe per file? I am currently thinking about approaching it with static code analysis and ESLint, but I'm open to other solutions as well.

Samples:

Example of a violation:

describe("Test 1", function () {
    it("should do something", function () {
        expect(true).toBe(true);
    });
});

describe("Test 2", function () {
    it("should do something else", function () {
        expect(false).toBe(false);
    });
});

If there is a single describe block, but it contains nested describes, it should not be reported as a violation. In other words, this is okay to have:

describe("Test 1", function () {
    it("should do something", function () {
        expect(true).toBe(true);
    });

    describe("Test 2", function () {
        it("should do something else", function () {
             expect(false).toBe(false);
        });
    });
});


Solution 1:[1]

The tricky part is flagging only describe blocks that are not nested, or "top-level" describes. Luckily, this is totally possible with ESLint!

ESLint "visits" the nodes two times while traversing the abstract syntax tree (AST, for short) of your JavaScript code: once while going down the tree, and another while going back up. The tree is traversed depth first, so if for example you have 3 describe blocks in your code like this:

describe("Test 1", function () {
    it("should do something", function () {
        expect(true).toBe(true);
    });

    describe("Test 2", function () {
        it("should do something else", function () {
             expect(false).toBe(false);
        });
    });
});

describe("Test 3", function () {
    it("should do something", function () {
        expect(true).toBe(true);
    });
});

The nodes would be visited in the following order:

enter "Test 1" -> enter "Test 2" -> exit "Test 2" -> exit "Test 1" -> enter "Test 3" -> exit "Test 3"

That means we just have to keep track of all the describe calls in a stack while going "down" a subtree, then pop them one at a time while going "up" that subtree. if while going up there is only one node left to pop from the stack, then that node is a "top-level" describe.

At the end, if we found more than on "top-level" describe, then our rule should report an error. I whipped up a small working prototype for you: https://astexplorer.net/#/3vMUwQjfpD/2

Solution 2:[2]

With the help of @vitorbal's sample code for the rule, the pull request with the new max-top-level-suites rule is now merged into and is a part of eslint-plugin-mocha ESLint plugin.

Solution 3:[3]

You can use no-restricted-syntax rule.

The selector that you want to use is: Program > ExpressionStatement[expression.type='CallExpression'][expression.callee.type='Identifier'][expression.callee.name='describe'] ~ ExpressionStatement[expression.type='CallExpression'][expression.callee.type='Identifier'][expression.callee.name='describe']

Complete example:


    "no-restricted-syntax": [2,
      {
        "selector": "Program > ExpressionStatement[expression.type='CallExpression'][expression.callee.type='Identifier'][expression.callee.name='describe'] ~ ExpressionStatement[expression.type='CallExpression'][expression.callee.type='Identifier'][expression.callee.name='describe']",
        "message": "Only one top level describe per test file"
      }
    ],

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 vitorbal
Solution 2 alecxe
Solution 3 pauldcomanici