Jest: Per-worker setup/teardown

Created on 17 Jul 2019  路  8Comments  路  Source: facebook/jest

馃殌 Feature Proposal

Jest provides _once-global_ setup and teardown, and _per-file_ setup and teardown, but conspicuously absent is _per-worker_ setup and teardown.

Motivation

One worker's set of tests runs serially, and is ideally suited to reusing an exclusive resource like a database or redis connection.

Example

Sue has a test suite that uses a database. She can create _multiple_ databases, but there is a time cost associated with spinning one up before it can be used.

From her test suite she'd like to get maximum concurrency with minimum database setup time.

Currently possible approaches:
1) One database, run all the tests serially.

  • This is unacceptably slow. Concurrency is one of Jest's killer features and it's sad to lose it.
    2) Multiple databases, created on fly at runtime, per-file. (in e.g. beforeAll())
  • This pays the database setup cost many more times than is necessary, slowing down the test suite.
    3) Multiple databases, created upfront. (e.g., make 20 databases, once, in globalSetup)
  • This requires you to choose a constant (e.g. 20) according to how many workers you want to run.
  • Choose the wrong number for -w (or get the wrong number inferred by your CPU core count), and it will either do too much work or not have enough databases.
  • Also it's painful to wait on a large upfront cost when you're running, say, just one test.

Ideally, instead:

4) Multiple databases, created once per-worker

  • This is the best-case for minimizing database setup and maximizing reuse, and doesn't require you to premeditate the number of databases.
  • You can use whatever -w you want (or let Jest infer it), and you'd get the exact right number of databases.
  • When you run just one test, you'll get just one database.

Pitch

Why does this feature belong in the Jest core platform?

I'm not sure where the boundary of _core platform_ is, but it stands to reason that beforeWorker and afterWorker should be provided by the same system that provides beforeAll, beforeEach, and globalSetup.

Alternatives

I googled around but couldn't come up with another way to achieve this. Is there another (perhaps undocumented) way to hook into worker setup/teardown?

Feature Request

Most helpful comment

A big +1 for this 馃憤馃徎

I'm from the core team maintaining Detox (who have already introduced enhancements to Jest, in the past), and we would also really appreciate this kind of support. Our use case is effectively identical, up to the point that instead of databases, we have Android/iOS emulators as the resource in question. We already have the means to bring up emulators on a per-worker basis, but we lack the ability to clean up efficiently (i.e. immediately when a worker is no longer required). Things become even more prominent when these emulators are _rented_ from external SaaS providers such as Genymotion cloud: You literally pay for rent time, and hence must painfully optimize it.

All 8 comments

https://jestjs.io/docs/en/configuration#setupfilesafterenv-array

But AFAIK there's no option to run something before the worker is closed. I'd find such a feature useful, especially for generating database namespaces to run tests in isolation etc., like with Redis you mentioned.

For more complex use cases, a custom environment is probably more well suited. We do this e.g. for setting up and tearing down a database connection.

@jeysal by "custom environment", you mean the thing specified via testEnvironment, right? Does that give you an opportunity for setup/teardown per-worker, or only per-file?

Well, not exactly per-worker. You can use JEST_WORKER_ID to distinguish between workers for using distinct databases, but I can see how that might not be enough in some use cases, performance-wise.

A big +1 for this 馃憤馃徎

I'm from the core team maintaining Detox (who have already introduced enhancements to Jest, in the past), and we would also really appreciate this kind of support. Our use case is effectively identical, up to the point that instead of databases, we have Android/iOS emulators as the resource in question. We already have the means to bring up emulators on a per-worker basis, but we lack the ability to clean up efficiently (i.e. immediately when a worker is no longer required). Things become even more prominent when these emulators are _rented_ from external SaaS providers such as Genymotion cloud: You literally pay for rent time, and hence must painfully optimize it.

Have any of you folks found a way to emulate this feature using the existing setup / teardown primitives?

@airhorns I've tried doing so by registering a process.on('beforeExit', () => {...}) callback in a worker's context (reference).
Unfortunately, empirically it seems that Jest keeps all workers alive right until the last one is done, so there's no added value compared to subscribing a global-teardown listener (which is the obvious alternative).

In addition, it seems Jest doesn't approve asynchronous work done past the time it expects everything to be torn down -- you get the famous warning saying Jest is still alive 1 second after it should have ended, and can't distinguish this cause for it from a real problem with your tests/code. That is what happens at best, actually, and at worst - your callback is force-killed prematurely along with the worker itself.

@d4vidi that makes sense, thanks for the tip! Are you running without jest's module resetting stuff so that you keep a handle on your per-worker shared resource throughout the run? I have been trying to get the same thing going but struggling to keep any kind of persistent state between suites because of the module reset, which seems desirable for other reasons.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ianp picture ianp  路  3Comments

mmcgahan picture mmcgahan  路  3Comments

samzhang111 picture samzhang111  路  3Comments

gustavjf picture gustavjf  路  3Comments

jardakotesovec picture jardakotesovec  路  3Comments