Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jest - Understanding execution order of a describe() and it()

I would like to understand why all the blocks describe() run before the it() in inside of each describe().

Here's a CodeSandbox example with a log per each describe and it block.

describe("sum A", () => {
  window.innerWidth = 100;
  console.log("describe A", window.innerWidth);

  beforeAll(() => {
    window.innerWidth = 105;
    console.log("describe A before", window.innerWidth);
  });

  it("1. should add 1 and 2", () => {
    console.log("it A1", window.innerWidth);
    expect(1 + 2).toBe(3);
  });
});

describe("sum B", () => {
  console.log("describe B", window.innerWidth, "why does it run before it A1?");

  beforeAll(() => {
    window.innerWidth = 205;
    console.log("describe B before", window.innerWidth);
  });

  it("1. should add 1 and 2", () => {
    console.log("it B1", window.innerWidth);
    expect(1 + 2).toBe(3);
  });
});

You'll notice that the log from the second describe block runs before the it() inside the first describe block.

Why does that happen? Should we avoid doing stuff in that part of the code and prefer using beforeAll() when we need to share and scope data in that describe block?

like image 656
sandrina-p Avatar asked Dec 05 '22 09:12

sandrina-p


1 Answers

When Jest runs it looks for all of the test files and runs each one.

Each test file runs within an environment provided by Jest that includes globals like describe, it, beforeAll, etc. All of these globals have a callback parameter that defines their behavior.

When a test file runs the top-level code runs...including any top-level describe calls.

When a describe runs it registers a test suite, and then its callback is called immediately.

This is different than it, beforeAll, beforeEach, etc. where the callback is recorded but not immediately called.

This means that all describe callback functions are called depth-first in the order they appear in a test file, as can be seen in this simple example:

describe('1', () => {
  console.log('1');
  describe('2', () => { console.log('2'); });
  describe('3', () => {
    console.log('3');
    describe('4', () => { console.log('4'); })
    describe('5', () => { console.log('5'); })
  })
  describe('6', () => { console.log('6'); })
})
describe('7', () => {
  console.log('7');
  it('(since there has to be at least one test)', () => { });
})

...which logs 1 - 7 in order.

This initial run of all the describe callbacks is called the collection phase during which the test suites are defined and all of the callbacks for any beforeAll, beforeEach, it, test, etc. are collected.

After the collection phase completes, Jest...

runs all the tests serially in the order they were encountered in the collection phase, waiting for each to finish and be tidied up before moving on.

For each test (each callback function registered with the global it or test functions) Jest links together any before callbacks, the test callback itself, and any after callbacks and runs the resulting functions in order.


Should we avoid doing stuff in that part of the code and prefer using beforeAll() when we need to share and scope data in that describe block?

For simple stuff that isn't shared it's fine to have it in the describe:

describe('val', () => {
  const val = '1';

  it('should be 1', () => {
    expect(val).toBe('1');  // Success!
  });
});

...but code in the describe can cause issues with shared data:

describe('val', () => {
  let val;

  describe('1', () => {
    val = '1';
    it('should be 1', () => {
      expect(val).toBe('1');  // FAIL! (val gets set to 2 in the second describe)
    })
  })

  describe('2', () => {
    val = '2';
    it('should be 2', () => {
      expect(val).toBe('2');  // Success!
    })
  })

});

...which can be fixed either by using before calls:

describe('val', () => {
  let val;

  describe('1', () => {
    beforeEach(() => {
      val = '1';
    });
    it('should be 1', () => {
      expect(val).toBe('1');  // Success!
    })
  })

  describe('2', () => {
    beforeEach(() => {
      val = '2';
    });
    it('should be 2', () => {
      expect(val).toBe('2');  // Success!
    })
  })

});

...or simply scoping the data to the describe:

describe('val', () => {

  describe('1', () => {
    const val = '1';
    it('should be 1', () => {
      expect(val).toBe('1');  // Success!
    })
  })

  describe('2', () => {
    const val = '2';
    it('should be 2', () => {
      expect(val).toBe('2');  // Success!
    })
  })

});

In your example you are using window.innerWidth which is a shared global, so you will want to use before functions since it can't be scoped to the describe.


Also note that you can't return anything from a describe so if your tests require any asynchronous setup then you will need to use a before function where you can return a Promise for Jest to await:

const somethingAsync = () => Promise.resolve('1');

describe('val', () => {
  let val;

  beforeAll(async () => {
    val = await somethingAsync();
  });

  it('should be 1', () => {
    expect(val).toBe('1');  // Success!
  });
});
like image 149
Brian Adams Avatar answered Dec 07 '22 22:12

Brian Adams