alecxe alecxe - 2 months ago 20
Javascript Question

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
decribe
s 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
describe
s, 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);
});
});
});

Answer

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