Configure different Jest timeouts for unit and integration tests in the same project
nausaf
Posted on October 17, 2022
The Problem
You want to configure different test execution timeouts for Jest tests in different folders in the same project e.g. 1 second for tests in
./tests/unitTests/
and 60 seconds for tests in./tests/integrationTests
.-
During debugging, the timeout for tests in any folder should be quite large, say 10 minutes.
Otherwise Jest could throw timeout errors like
thrown: "Exceeded timeout of 5000 ms for a test.
even if a test completes successfully. This happens when debugging a test takes longer than the (default or explicitly configured) timeout for it.
Solution
Given the folder structure:
.
│ jest.config.js
│ package-lock.json
│ package.json
│
└───tests
├───integrationTests
│ integrationTestSuite.test.js
│
└───unitTests
unitTestSuite.test.js
a solution is as follows:
npm install --save-dev debugger-is-attached
-
Create a
jestSetup.js
file in each of the test folders and set the desired timeout via a call tojest.setTimeout()
method.
So we have atests/unitTests/jestSetup.js
file:
jest.setTimeout(1000); //timeout of 1 second
and a
tests/integrationTests/jestSetup.js
file:
jest.setTimeout(60000); //timeout of 1 minute
-
Create
jest.config.js
in project root as follows
const { debuggerIsAttached } = require("debugger-is-attached"); const path = require("path"); module.exports = async () => { const isDebuggerAttached = await debuggerIsAttached(); const unitTestFolder = "<rootDir>/tests/unitTests"; const integrationTestFolder = "<rootDir>/tests/integrationTests"; const getSetupFiles = (folder) => isDebuggerAttached ? [] : [path.join(folder, "jestSetup.js")]; const baseProjectConfig = { //Here put any properties that are the same for //all folders and can be specified at level //of the project object (all such properties //are declared in type ProjectConfig in //Config.ts in Jest repo) }; let config = { //any config key/values in configuration (except those //that are to specified in ProjectConfig) //e.g. //collectCoverage: true, //project config projects: [ { ...baseProjectConfig, displayName: "UnitTests", testMatch: [path.join(unitTestFolder, "**/*.test.js")], slowTestThreshold: 1000, //1 second setupFilesAfterEnv: getSetupFiles(unitTestFolder), }, { ...baseProjectConfig, displayName: "IntegrationTests", testMatch: [path.join(integrationTestFolder, "**/*.test.js")], slowTestThreshold: 60000, //1 minute setupFilesAfterEnv: getSetupFiles(integrationTestFolder), }, ], //any other Jest config goes here //(these are any properties declared in type //InitialConfig but not in type ProjectConfig //in Config.ts in Jest repo) }; if (isDebuggerAttached) config["testTimeout"] = 600000; //ten minutes return config; };
-
In User Settings (
settings.json
which appears when from the Ctrl + P command pallette you select Preferences: Open User Setting (JSON)), set"jest.monitorLongRun"
to a value that is equal to or greater than the largest of the folder-specific timeouts declared injest.config.js
above. For example,
"jest.monitorLongRun": 60000, //1 minute
Explanation
Configuring different timeouts for different test folders
testTimeout
property can be set in jest.config.js
in project root to set a timeout other than the default of 5s:
module.exports = {
...
testTimeout: 60000; //60 seconds
};
How to specify testTimeout
separately for different subfolders?
The canonical way to assign different configurations to tests in different subfolders is to use Jest's monorepo configuration. We pretend, as far as Jest is concerned, that folders tests/integrationTests
and tests/unitTests
are two separate projects in a monorepo (a collection of related projects stored in a single repository).
Using this approach we can configure separate timeouts for our two subfolders as follows:
- Create a
jest.config.js
in each subfolder. In this settestTimeout
property as shown in the snippet above. -
Declare the different folder-specific config files as projects in the top-level
jest.config.js
:
module.exports = { projects: [ "<rootDir>/tests/unitTests/jest.config.js", "<rootDir>/tests/integrationTests/jest.config.js", ], };
At this point the folder structure would be as follows:
.
│ jest.config.js
│ package-lock.json
│ package.json
│
└───tests
├───integrationTests
│ integrationTestSuite.test.js
│ jest.config.js
│
└───unitTests
jest.config.js
unitTestSuite.test.js
The problem is that if we configure testTimeout
property in a folder-specific config file it will not have any effect.
This is because testTimeout
is not defined in type ProjectConfig
in Config.ts
which specifies the config schema of project
(which for use are the two subfolders of tests).
Even though the top-level as well as folder-specific config files are all called jest.config.js
, the properties that are allowed in folder level config are not all the same as the properties allowed in top level config.
Folder-specific config files need to have be those defined in type ProjectConfig
whereas top-level jest.config.js
specifies properties that are probably declared in type InitialConfig
in Config.ts
.
Many keys are defined in both types but not testTimeout
: it is only contained in InitialConfig
and therefore only has effect if declared in the top-level config file.
Therefore testTimeout
property cannot be use to override test timeouts in jest.config.js
files in subfolders.
Instead, to set timeout at subfolder level:
- We can call
jest.setTimeout(TIMEOUT_IN_MS)
in a.js
file in the subfolder, conventionally namedjestSetup.js
. - Declare
jestSetup.js
in the folder-leveljest.config.js
so that it would be run by Jest before any tests in that folder are executed.
For example create tests/integrationTests/jestSetup.js
as follows:
jest.setTimeout(60000); //timeout of 1 minute
and a tests/integrationTests/jest.config.js
to go with it:
module.exports = {
displayName: "IntegrationTests",
setupFilesAfterEnv: [`<rootDir>/jestSetup.js`],
};
For unit tests, ./tests/unitTests/jest.config.js
would be the same as the config file for integration tests folder shown above (because <rootDir>
always resolves to the containing folder). However ./tests/integrationTests/jestSetup.js
would specify a different timeout:
jest.setTimeout(1000); //timeout of 1 second
The folder structure of this working solution looks like this:
.
│ jest.config.js
│ package-lock.json
│ package.json
│
└───tests
├───integrationTests
│ integrationTestSuite.test.js
│ jest.config.js
| jestSetup.js
│
└───unitTests
jest.config.js
jestSetup.js
unitTestSuite.test.js
We can eliminate folder-specific jest.config.js
files by pulling the info in the top level config so the ./jest.config.js
in the root now looks like this:
module.exports = {
projects: [
{
displayName: "UnitTests",
testMatch: ["<rootDir>/tests/unitTests/**/*.test.js"],
setupFilesAfterEnv: ["<rootDir>/tests/unitTests/jestSetup.js"],
},
{
displayName: "IntegrationTests",
testMatch: ["<rootDir>/tests/integrationTests/**/*.test.js"],
setupFilesAfterEnv: ["<rootDir>/tests/integrationTests/jestSetup.js"],
},
],
};
The folder structure of this more compact solution looks like this:
.
│ jest.config.js
│ package-lock.json
│ package.json
│
└───tests
├───integrationTests
│ integrationTestSuite.test.js
│ jestSetup.js
│
└───unitTests
jestSetup.js
unitTestSuite.test.js
I'd like to point out three improvements that can be made:
-
It is worth adding a
slowTestThreshold
property in everyproject
object and set it equal to or somewhat greater than the timeout declared in the correspondingjestSetup.js
.A longer timeout prevents Jest from throwing an error on longer running tests. But it would still show the execution time of such a tests with a red background:
Adding
slowTestThreshold: 60000
to the folder's config injest.config.js
tells Jest that this is how long you expect tests in that folder to take so it doesn't show execution times in warning colour. -
If a property is exported both
ProjectConfig
andInitialConfig
types then, if you configure it in top leveljest.config.js
but not in folder-level config, it would be overridden by project config to its default value which would probably benull
orundefined
.For example given a
jest.config.js
that looks like this:
module.exports = { setupFiles = ['./topLevelSetupFile.js'], ... projects: [ { displayName: "IntegrationTests", testMatch: ["<rootDir>/tests/integrationTests/**/*.test.js"], setupFilesAfterEnv: ["<rootDir>/tests/integrationTests/jestSetup.js"], } ]; };
In the effective folder-specific configuration for folder
./tests/integrationTests/
,setupFiles
property would be set to nothing (null
I think).A nice solution (from Orlando Bayo's post) is to set the shared config - properties that are shared across all folders - in a
baseProjectconfig
object and spread this into everyproject
object.
Incorporating both of these improvements into our solution, we have the following jest.config.js
:
const baseProjectConfig = {
//Here put any properties that are the same for
//all folders and can be specified at level
//of the project object (all such properties
//are declared in type ProjectConfig in
//Config.ts in Jest repo)
};
let config = {
//any config key/values in configuration (except those
//that are specified in ProjectConfig)
//e.g.
//collectCoverage: true,
//project config
projects: [
{
...baseProjectConfig,
displayName: "UnitTests",
testMatch: [path.join(unitTestFolder, "**/*.test.js")],
slowTestThreshold: 1000, //1 second
setupFilesAfterEnv: getSetupFiles(unitTestFolder),
},
{
...baseProjectConfig,
displayName: "IntegrationTests",
testMatch: [path.join(integrationTestFolder, "**/*.test.js")],
slowTestThreshold: 60000, //1 minute
setupFilesAfterEnv: getSetupFiles(integrationTestFolder),
},
],
//any other Jest config goes here
//(these are any properties declared in type
//InitialConfig but not in type ProjectConfig
//in Config.ts in Jest repo)
};
module.exports = config
-
Jest VS Code extension still shows a popup if a test takes too long to execute (although, because of the configuration above, the test won't fail on a timeout):
To prevent this warning window from popping up, set
"jest.monitorLongRun"
in User Setting file (to edit it select Preferences: Open User Settings from Ctrl + P command pallette) to the value of the longest of your folder-specific timeouts e.g.:
"jest.monitorLongRun": 60000, //1 minute
Setting a long timeout when debugging
When debugging, if you take longer to step through a test than the (default or explicitly configured) timeout, Jest would throw a timeout error at the end even if the test completed successfully. I find that for a test to fail like this is confusing when you're debugging a test that you expect to pass.
To address this issue we can use the debugger-is-attached
package. This exports an async function that returns true
if debugger is attached and false
otherwise.
It can be integrated into jest.config.js
by exporting an async
function that returns the config object instead of directly returning the object in question.
const { debuggerIsAttached } = require("debugger-is-attached");
module.exports = async () => {
//do async things
...
return {
//the config object
}
}
Inside the function, if the debugger is not attached then everything should be the same as before but if it is attached, then we make two changes to the returned object:
not configure
jestSetup.js
, the file that declares the timeout for tests in its containing folder, to run.set
testTimeout
property to 10 minutes (600000
ms) to give us plenty of time to debug a test.
After these changes, the final top-level jest.config.js
is as shown in TL;DR at the top.
Thanks for reading. Any comments or suggestions for improvement would be greatly appreciated.
Posted on October 17, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
October 17, 2022