Search code examples
javascriptjasmineprotractoreslintstatic-code-analysis

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

  • 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