Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jest/Jasmine: Weird Execution Order Of beforeEach()

Given the class:

export class Foo {
  private static _count = 0;
  id: number;
  constructor() {
    this.id = ++Foo._count; // starts with 1
  }
}

And the test suite:

describe('Foo Class', () => {
  describe('constructor', () => {
    const fooClassRef = Foo as any; // to pass typechecking
    beforeEach(() => {
      console.log(`start of beforeEach: ${fooClassRef._count}`);
      fooClassRef._count = 0;
      console.log(`end of beforeEach: ${fooClassRef._count}`);
    });
    describe('creating one Foo obj', () => {
      console.log(fooClassRef._count);
      const foo = new Foo();
      it('should have an id of 1', () => {
        expect(foo.id).toBe(1);
      });
    });
    describe('creating two Foo objs', () => {
      console.log(fooClassRef._count);
      const foo1 = new Foo();
      const foo2 = new Foo();
      it('should have ids of 1 and 2', () => {
        expect(foo1.id).toBe(1);
        expect(foo2.id).toBe(2);
      });
    });
  });
});

The second test fails:

expect(received).toBe(expected) // Object.is equality

    Expected: 1
    Received: 2

       |       const foo2 = new Foo();
       |       it('should have ids of 1 and 2', () => {
    >  |         expect(foo1.id).toBe(1);
       |                         ^
       |         expect(foo2.id).toBe(2);
       |       });
       |     });

The resulting log is:

0
1
start of beforeEach(): 3
end of beforeEach(): 0
start of beforeEach(): 0
end of beforeEach(): 0

Seems like the beforeEach code isn't actually running until all the tests have already finished.

like image 621
ZackDeRose Avatar asked Jan 13 '19 07:01

ZackDeRose


People also ask

Does Jasmine run tests in order?

Currently (v2. x) Jasmine runs tests in the order they are defined.

Does beforeEach run before each describe?

beforeEach(fn) This is often useful if you want to reset some global state that will be used by many tests. Here the beforeEach ensures that the database is reset for each test. If beforeEach is inside a describe block, it runs for each test in the describe block.

What is the difference between beforeAll and beforeEach?

If you're certain that the tests don't make any changes to those conditions, you can use beforeAll (which will run once). If the tests do make changes to those conditions, then you would need to use beforeEach , which will run before every test, so it can reset the conditions for the next one.

What is beforeEach in Jasmine?

JasmineJS - beforeEach() Advertisements. Another notable feature of Jasmine is before and after each function. Using these two functionalities, we can execute some pieces of code before and after execution of each spec. This functionality is very useful for running the common code in the application.


1 Answers

[This answer is relevant for both Jest and Jasmine!]

The error in the example above is a misunderstanding of how nesting describe, it, and setup/teardown callbacks work.

(Note that for jest it is just an alias for test)

Imagine if you added a beforeEach before every describe/it call above so that the nesting looks like so:

┬─ beforeEach 1
└┬ describe Foo Class
 ├─ beforeEach 2
 └┬ describe constructor
  ├── beforeEach 3
  ├─┬ describe creating one Foo obj
  │ ├─ * - originally constructed a Foo here (but not inside of a beforeEach)
  │ ├─ beforeEach 4A
  │ └─ it should have an id of 1
  └─┬ describe creating two Foo objs
    ├─ * - originally constructed 2 more Foo's here (but not inside of a beforeEach)
    ├─ beforeEach 4B
    └─ it should have ids of 1 and 2

The way the order of the describe, beforeEach, and it callbacks will be run is:

  1. Code inside of describe callbacks are ultimately run first. You can think of the describe code's job to be registering it/test callbacks and beforeEach callbacks (as well as beforeAll afterAll and afterEach callbacks too!). There really shouldn't be any code inside of your describe's (aside from declaring var's to reference) that isn't nested inside of an it or beforeEach callback - this is ultimately why your test was originally failing.

  2. Jest will register each it/test callback as a "Test" to be run, and will ensure that all beforeAll, beforeEach, afterAll, and afterEach callbacks are run appropriately to their nesting.

Following this methodology given the hypothetical tree (where every layer has a beforeEach), this results in the following order:

  1. originally constructed a Foo here (but not inside of a beforeEach)
  2. originally constructed 2 more Foo's here (but not inside of a beforeEach)
  3. beforeEach 1
  4. beforeEach 2
  5. beforeEach 3
  6. beforeEach 4A
  7. it should have an id of 1
  8. beforeEach 1
  9. beforeEach 2
  10. beforeEach 3
  11. beforeEach 4B
  12. it should have ids of 1 and 2

Which explains the original order of the logs.

To better test this, let's instead use the follow test code:

beforeEach(() => {
  console.log(1);
  const fooClassRef = Foo as any;
  fooClassRef._count = 0;
});
describe('Foo Class', () => {
  beforeEach(() => console.log(2));
  describe('constructor', () => {
    beforeEach(() => console.log(3));
    describe('creating one Foo obj', () => {
      beforeEach(() => console.log('4A'));
      test('should have an id of 1', () => {
        console.log('A');
        const foo = new Foo();
        expect(foo.id).toBe(1);
      });
    });
    describe('creating two Foo objs', () => {
      let foo1;
      let foo2;
      beforeEach(() => {
        console.log('4B');
        foo1 = new Foo();
        foo2 = new Foo();
      });
      it('should have ids of 1 and 2', () => {
        console.log(`4B'`);
        expect(foo1.id).toBe(1);
        expect(foo2.id).toBe(2);
      });
      it('should originally start with ids of 1 and 2, but they could be changed', () => {
        console.log(`4B''`);
        expect(foo1.id).toBe(1);
        expect(foo2.id).toBe(2);
        foo2.id = 47;
        expect(foo1.id).toBe(1);
        expect(foo2.id).toBe(47);
      });
    });
  });
});

Note that:

  • We moved the resetting of the private static property to a beforeEach at the top level of the test suit - since resetting that for every Foo test you could run would probably would a good idea if Foo had other methods or logic to test.
  • We added another test to the "creating two Foo objs" describe
  • We added a beforeEach where we create our foo1 and foo2 to that describe since that is setup that we'll want to do for both of our "creating two Foo objs" tests!

Now, all our tests pass, and the resulting log of this test suite is:

1
2
3
4A
1
2
3
4B
4B'
1
2
3
4B
4B''

References

  • https://jestjs.io/docs/en/setup-teardown#scoping
  • https://jestjs.io/docs/en/setup-teardown#order-of-execution-of-describe-and-test-blocks
  • https://www.reddit.com/r/javascript/comments/9sy33o/whats_the_difference_of_jests_describe_and_test/
like image 126
ZackDeRose Avatar answered Sep 22 '22 12:09

ZackDeRose