15

I have a situation where I need to create a test that calls a function and checks its return value. It returns an object so I have to iterate through it and check 100 or so values for correctness. If one of them fails I want to know which one.

I cannot work out how to do this with vanilla Jest such that the test is self-contained and I get a meaningful error message on a failure.

For example, I can do this: (pseudocode to illustrate, not actual code)

describe('Test for function A', () => {
    beforeAll('Create class instance', () => {
        this.inst = new MyClass();
    });

    test('Call function with no parameters', () => {
        const value = this.inst.run();

        for (each key=value) {
            expect(value).toBe(correct); // on failure, doesn't tell me the key, only the value
        }
    });
});

The problem with this is that if value is not correct then the error message is not very helpful, as it doesn't tell me which of the 100 values has the problem.

I can't change to test.each() because then I get an error saying I have nested test() calls which is not allowed.

If I use an inner test() and change the parent test() to describe() then the code becomes this:

describe('Test for function A', () => {
    beforeAll('Create class instance', () => {
        this.inst = new MyClass();
    });

    describe('Call function with no parameters', () => {
        const value = this.inst.run();

        for (each value) {
            test(`Checking ${value.name}`, () => {
                expect(value).toBe(correct);
            });
        }
    });
});

This would give me a detailed error message except this.inst.run() is called during test set up, before this.inst has been set by beforeAll(), so it fails. (Jest runs all the describe() blocks first, then beforeAll(), then test(). This means I call this.inst.run() first in a describe() block before the beforeAll() block creates the class instance.)

Is there any way that this is possible to achieve? To have a test that requires an object created and shared amongst all the child tests, a test group that calls a function to get data for that group, then a bunch of tests within the group?

2
  • You need to declare at least one test inside describe, in this case the first describe
    – lissettdm
    Commented Mar 3, 2021 at 12:54
  • @lissettdm: Could you explain how that will fix the problem? My real code has other tests in each describe(), I just omitted them for clarity.
    – Malvineous
    Commented Mar 6, 2021 at 11:48

3 Answers 3

11

Yes, it is possible according to the order of execution of describe and test blocks:

describe("Test for function A", () => {
  this.inst = new MyClass();
  afterAll("Create class instance", () => { //--> use this instead of beforeAll
    this.inst = new MyClass();
  });

  test("Should be defined", () => {
    //--> at least one test inside describe
    expect(inst).toBeTruthy();
  });

  describe("Call function with no parameters", () => {
    const value = this.inst.run();

    test("Should be defined", () => {
      //--> at least one test inside describe
      expect(value).toBeTruthy();
    });

    for (/*...each value */) {
      test(`Checking ${value.name}`, () => {
        expect(value).toBe(correct);
      });
    }
  });
});
1
  • Is there a way to do this such that this.inst.run() is also inside a test, so that I can pick it up as a test failure if it throws an exception? Otherwise it looks like it could work, I will try it, thanks!
    – Malvineous
    Commented Mar 6, 2021 at 11:46
3

I came up with a workaround for this. It's a bit hacky but it seems to work. Essentially you use promises to wrap the value you're interested in, so one test will sit there await-ing the result from another test.

Obviously this will only work if the tests are run in parallel, or if the sequential ordering is such that the promise is resolved before it is awaited.

The only trick below is that the await is placed in a beforeAll() block, so that the value is available to all tests within that describe() section. This avoids the need to await in each individual test.

The benefit of this is that the test set up (creating the object) is within a test() so exceptions are captured, and the checks themselves (expect().toBe()) are in separate tests so that the test name can be set to something descriptive. Otherwise if your expect() calls are in a for loop, when one fails there's no way to figure out which array entry was at fault.

It's a lot of work just because you can't supply a description on the expect() call (unlike other testing frameworks), but if you're stuck with Jest then this does at least work. Hopefully one day they will add a per-expect description to avoid all this.

Here is some sample pseudocode:

describe('Test for function A', () => {
    let resolveValue;
    let promiseValue = new Promise(resolve => resolveValue = resolve);

    describe('Create class instance', () => {
        test('Run processing', () => {
            this.inst = new MyClass();
            // inst.run() is now called inside a test(), so any failures will be caught.
            const value = this.inst.run();
            resolveValue(value); // release 'await promiseValue' below
        });
    });

    describe('Call function with no parameters', () => {
        let value; // this is global within this describe() so all tests can see it.

        beforeAll(async () => {
            // Wait for the above test to run and populate 'value'.
            value = await promiseValue;
        });

        for (each value) {
            // Check each value inside test() to get a meaningful test name/error message.
            test(`Checking ${value.name}`, () => {
                // 'value' is always valid as test() only runs after beforeAll().
                expect(value).toBe(correct);
            });
        }
    });
});
1
  • 1
    quite underrated answer. I've been stuck trying to handle several jest tests for a long time and your solution helped me to make a breakthrough. Commented Sep 19, 2023 at 2:17
0

describe('AssistantController', () => {

  let assistantId: string;
  let resolve: (...args: any[]) => void;
  let resolve1: (...args: any[]) => void;
  const p = new Promise((res) => (resolve = res));
  const p1 = new Promise((res) => (resolve1 = res));

  it('part I', async() => {
    // ...
    resolve();
  });

  it('part II', () => {
    p.then(async() => {
      // ...
      resolve1();
    });
  });

  it('part III', () => {
    p1.then(async() => {
      // ...
    });
  });
});

Not the answer you're looking for? Browse other questions tagged or ask your own question.