jest doesn't wait beforeAll resolution to start tests
Asked Answered
T

4

16

What I test: An express server endpoints

My goal: automate API tests in a single script

What I do: I launch the express server in a NodeJS child process and would like to wait for it to be launched before the test suite is run (frisby.js endpoints testing)

What isn't working as expected: Test suite is launched before Promise resolution

I rely on the wait-on package which server polls and resolves once the resource(s) is/are available.

const awaitServer = async () => {
  await waitOn({
    resources: [`http://localhost:${PORT}`],
    interval: 1000,
  }).then(() => {
    console.log('Server is running, launching test suite now!');
  });
};

This function is used in the startServer function:

const startServer = async () => {
  console.log(`Launching server http://localhost:${PORT} ...`);

  // npmRunScripts is a thin wrapper around child_process.exec to easily access node_modules/.bin like in package.json scripts
  await npmRunScripts(
    `cross-env PORT=${PORT} node -r ts-node/register -r dotenv/config src/index.ts dotenv_config_path=.env-tests`
  );

  await awaitServer();
}

And finally, I use this in something like

describe('Endpoints' () => {
  beforeAll(startTestServer);

  // describes and tests here ...
});

Anyway, when I launch jest the 'Server is running, launching test suite now!' console.log never shows up and the test suite fails (as the server isn't running already). Why does jest starts testing as awaitServer obviously hasn't resolved yet?

The npmRunScripts function works fine as the test server is up and running a short while after the tests have failed. For this question's sake, here's how npmRunScripts resolves:

// From https://humanwhocodes.com/blog/2016/03/mimicking-npm-script-in-node-js/
const { exec } = require('child_process');
const { delimiter, join } = require('path');

const env = { ...process.env };
const binPath = join(__dirname, '../..', 'node_modules', '.bin');

env.PATH = `${binPath}${delimiter}${env.PATH}`;

/**
 * Executes a CLI command with `./node_modules/.bin` in the scope like you
 * would use in the `scripts` sections of a `package.json`
 * @param cmd The actual command
 */
const npmRunScripts = (cmd, resolveProcess = false) =>
  new Promise((resolve, reject) => {
    if (typeof cmd !== 'string') {
      reject(
        new TypeError(
          `npmRunScripts Error: cmd is a "${typeof cmd}", "string" expected.`
        )
      );
      return;
    }

    if (cmd === '') {
      reject(
        new Error(`npmRunScripts Error: No command provided (cmd is empty).`)
      );
      return;
    }

    const subProcess = exec(
      cmd,
      { cwd: process.cwd(), env }
    );

    if (resolveProcess) {
      resolve(subProcess);
    } else {
      const cleanUp = () => {
        subProcess.stdout.removeAllListeners();
        subProcess.stderr.removeAllListeners();
      };

      subProcess.stdout.on('data', (data) => {
        resolve(data);
        cleanUp();
      });
      subProcess.stderr.on('data', (data) => {
        reject(data);
        cleanUp();
      });
    }
  });

module.exports = npmRunScripts;
Todhunter answered 20/3, 2019 at 10:56 Comment(0)
T
13

I found the solution. After trying almost anything, I didn't realize jest had a timeout setup which defaults at 5 seconds. So I increased this timeout and the tests now wait for the server promise to resolve.

I simply added jest.setTimeout(3 * 60 * 1000); before the test suite.

Todhunter answered 20/3, 2019 at 14:38 Comment(1)
What if in some cases my queries needs more time? I cant understand why I have to do that. Why jest simply does not wait for all my promises in beforeAll()Brianabriand
M
11

In my case, it caused by the flaw of the beforeAll part. Make sure the beforeAll doesn't contain any uncaught exceptions, otherwise it will behaves that the testing started without waiting for beforeAll resolves.

Milda answered 10/12, 2019 at 8:56 Comment(2)
spent too many hours debugging this same situation :'(Cinerary
This was also happening to me. Saved me a lot of time. Thank youVaudois
S
7

After much digging I found a reason for why my beforeAll didn't seem to be running before my tests. This might be obvious to some, but it wasn't to me.

If you have code in your describe outside an it or other beforeX or afterY, and that code is dependent on any beforeX, you'll run into this problem.

The problem is that code in your describe is run before any beforeX. Therefore, that code won't have access to the dependencies that are resolved in any beforeX.

For example:

describe('Outer describe', () => {
    let server;
    beforeAll(async () => {
        // Set up the server before all tests...
        server = await setupServer();
    });

    describe('Inner describe', () => {
        // The below line is run before the above beforeAll, so server doesn't exist here yet!
        const queue = server.getQueue(); // Error! server.getQueue is not a function
        it('Should use the queue', () => {
            queue.getMessage(); // Test fails due to error above
        });
    });
});

To me this seems unexpected, considering that code is run in the describe callback, so my impression was that that callback would be run after all beforeX outside the current describe.

It also seems this behavior won't be changed any time soon: https://github.com/facebook/jest/issues/4097

Spitzer answered 28/9, 2021 at 14:49 Comment(3)
I don't like this approach anyways. Tests should be isolated and therefore, avoid this kind of setup in a beforeAll implementation.Todhunter
This just bit me too. I refactored two 'it' blocks to factor out some common setup, and spent a day beating my head against why my tests were busted.Kwangju
In my case I just had to remove describe altogether, not a big deal.Choirboy
B
4

In newer versions of jest (at least >1.3.1) you can pass a done function to your beforeAll function and call it after everything is done:

beforeAll(async (done) => {
  await myAsyncFunc();
  done();
})
it("Some test", async () => {
  // Runs after beforeAll
})

More discussions here: https://github.com/facebook/jest/issues/1256

17 Apr 24 Edit: I think jest waits for beforeAll only if it is inside describe something like this:

describe("test", () => {
  beforeAll(async (done) => {
    await new Promise()
    done();
  });
})
Brianabriand answered 11/10, 2020 at 9:44 Comment(2)
This is handy! Thank you.Mckie
Not in version 27.Propagate

© 2022 - 2024 — McMap. All rights reserved.