-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
feat(Programmatic API): Add programmatic API #14062
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
908c73d
e2c65f8
6a75acf
7d70600
8155081
79dbfd5
d0fd9a9
b9e9278
d8f6e26
68c924f
68ac3ef
a375b0d
0f8f6f2
1935947
77723b3
a749243
48a5a94
ac2b879
e5c254f
59cfa1a
d570bc8
80d3c23
0b6f93d
6877134
26e87e1
f3e41ef
c3b95f8
2677e1d
8ec975a
ccc7e4e
f04118d
f8e0259
098cc21
65fe53f
9f1a113
8da9922
6a26f7f
f6d0c5a
fe45d4f
ef783ed
46a7ce9
9f056b8
92d6d44
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
--- | ||
id: programmatic-api | ||
title: Programmatic API | ||
--- | ||
|
||
:::caution | ||
|
||
The programmatic API is currently **experimental**. It is useful for advanced use cases only. You normally don't need to use it if you just want to run your tests. | ||
|
||
::: | ||
|
||
This page documents Jest's programmable API that can be used to run jest from `node`. TypeScript types are provided. | ||
|
||
## Simple example | ||
|
||
```js | ||
import {createJest} from 'jest'; | ||
|
||
const jest = await createJest(); | ||
jest.globalConfig = { | ||
collectCoverage: false, | ||
watch: false, | ||
...jest.globalConfig, | ||
}; | ||
const {results} = await jest.run(); | ||
console.log(`run success, ${result.numPassedTests} passed tests.`); | ||
``` | ||
|
||
## Programmatic API reference | ||
|
||
### `createJest(args: Partial<Config.Argv> = {}, projectPath = ['.']): Promise<Jest>` \[function] | ||
|
||
Create a Jest instance asynchronously. You can provide command line arguments (for example, `process.argv`) as first argument and a list of custom [projects](./Configuration.md#projects-arraystring--projectconfig) as the second argument. If no `projects`, were configured, the current project will be provided as project config. | ||
|
||
Examples: | ||
|
||
```js | ||
import {createJest} from 'jest'; | ||
|
||
const jest = await createJest(); | ||
const jest2 = await createJest({config: 'jest.alternative.config.js'}); | ||
``` | ||
|
||
### `jest.globalConfig` \[Readonly\<GlobalConfig>] | ||
|
||
The global config associated with this jest instance. It is `readonly`, so it cannot be changed in-place. In order to change it, you will need to create a new object. | ||
|
||
Example: | ||
|
||
```js | ||
jest.globalConfig = { | ||
...jest.globalConfig, | ||
collectCoverage: false, | ||
watch: false, | ||
}; | ||
``` | ||
|
||
### `jest.projectConfigs` \[Readonly\<ProjectConfig>\[]] | ||
|
||
A list of project configurations associated with this jest instance. They are `readonly`, so it cannot be changed in-place. In order to change it, you will need to create a new object. | ||
|
||
```js | ||
jest.projectConfigs = jest.projectConfigs.map(config => ({ | ||
...config, | ||
setupFiles: ['custom-setup.js', ...config.setupFiles], | ||
})); | ||
``` | ||
|
||
### `jest.run` \[function] | ||
|
||
Async function that performs the run. It returns a promise that resolves in a `JestRunResult` object. This object has a `results` property that contains the actual results. | ||
|
||
## Advanced use cases | ||
|
||
These are more advanced use cases that demonstrate the power of the api. | ||
|
||
### Overriding config options | ||
|
||
You can use `createJest` to create a Jest instance, and alter some of the options using `globalConfig` adn `projectConfigs`. | ||
|
||
```js | ||
import {createJest} from 'jest'; | ||
const jest = await createJest(); | ||
|
||
// Override global options | ||
jest.globalConfig = { | ||
...jest.globalConfig, | ||
collectCoverage: false, | ||
reporters: [], | ||
testResultsProcessor: undefined, | ||
watch: false, | ||
testPathPattern: 'my-test.spec.js', | ||
}; | ||
|
||
// Override project options | ||
jest.projectConfigs = jest.projectConfigs.map(config => ({ | ||
...config, | ||
setupFiles: ['custom-setup.js', ...config.setupFiles], | ||
})); | ||
|
||
// Run | ||
const {results} = await jest.run(); | ||
console.log(`run success, ${results.numPassedTests} passed tests.`); | ||
``` | ||
|
||
### Override options based on the configured options | ||
|
||
You might want to override options based on other options. For example, you might want to provide your own version of the `jsdom` or `node` test environment. | ||
|
||
```js | ||
import {createJest} from 'jest'; | ||
|
||
const jest = await createJest(); | ||
|
||
jest.projectConfigs = [ | ||
{ | ||
...jest.projectConfigs[0], | ||
// Change the test environment based on the configured test environment | ||
testEnvironment: overrideTestEnvironment(configs[0].testEnvironment), | ||
}, | ||
]; | ||
|
||
const {results} = await jest.run(); | ||
console.log(results); | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`createJest run programmatically: stdout 1`] = `"run success, 1 passed tests."`; | ||
|
||
exports[`createJest run programmatically: summary 1`] = ` | ||
"Test Suites: 1 passed, 1 total | ||
Tests: 1 passed, 1 total | ||
Snapshots: 0 total | ||
Time: <<REPLACED>> | ||
Ran all test suites." | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"testMatch": ["**/*.test.js"], | ||
"testEnvironment": "jsdom" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/** | ||
* Copyright (c) Meta Platforms, Inc. and affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
const {runCLI} = require('jest'); | ||
|
||
const config = { | ||
projects: [ | ||
{testEnvironment: 'jsdom', testMatch: ['<rootDir>/client/**/*.test.js']}, | ||
{testEnvironment: 'node', testMatch: ['<rootDir>/server/**/*.test.js']}, | ||
], | ||
}; | ||
|
||
runCLI({config: JSON.stringify(config)}, [process.cwd()]) | ||
.then(() => | ||
console.log('run-programmatically-cli-multiple-projects completed'), | ||
) | ||
.catch(err => { | ||
console.error(err); | ||
process.exitCode = 1; | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,18 +5,20 @@ | |
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
const {runCLI} = require('@jest/core'); | ||
const {createJest} = require('jest'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. instead of changing this test, can you add a new one? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @nicojs missed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've moved the old one to "run-cli.js", so this project now has 2 entry files. |
||
|
||
const config = { | ||
projects: [ | ||
{testMatch: ['<rootDir>/client/**/*.test.js']}, | ||
{testMatch: ['<rootDir>/server/**/*.test.js']}, | ||
], | ||
}; | ||
async function main() { | ||
const jest = await createJest(); | ||
jest.globalConfig = { | ||
collectCoverage: false, | ||
watch: false, | ||
...jest.globalConfig, | ||
}; | ||
await jest.run(); | ||
console.log('run-programmatically-core-multiple-projects completed'); | ||
} | ||
|
||
runCLI({config: JSON.stringify(config)}, [process.cwd()]) | ||
.then(() => console.log('run-programmatically-mutiple-projects completed')) | ||
.catch(err => { | ||
console.error(err); | ||
process.exitCode = 1; | ||
}); | ||
main().catch(err => { | ||
console.error(err); | ||
process.exitCode = 1; | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"testMatch": ["**/*.test.js"], | ||
"testEnvironment": "node" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,5 +6,7 @@ | |
*/ | ||
|
||
describe('server', () => { | ||
it('should work', () => {}); | ||
it('should work', () => { | ||
expect(typeof document).toBe('undefined'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you send a separate PR with this change? |
||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
/** | ||
* Copyright (c) Meta Platforms, Inc. and affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
import {createJest} from 'jest'; | ||
|
||
const jest = await createJest(); | ||
jest.globalConfig = { | ||
collectCoverage: false, | ||
watch: false, | ||
...jest.globalConfig, | ||
}; | ||
const {results} = await jest.run(); | ||
console.log(`run success, ${results.numPassedTests} passed tests.`); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
/** | ||
* Copyright (c) Meta Platforms, Inc. and affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
/* eslint-disable no-undef */ | ||
|
||
describe('jest-core', () => { | ||
it('should run this test', () => {}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should expose something that can be mutated in this way. I'd rather have a
mergeConfig
or some such that is invoked with the current config and expects to get a new config back it assigns internallyThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean something like this?
Or more like this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or
The latter would mean
globalConfig
was some sort of class or something instead of just the objectThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would you override a project-specific config?
I think allowing you to mutate config in-place makes more sense, actually. So removing the read-only feature. I don't think freezing the config serves a real purpose. 🤷♀️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I generally prefer encapsulation - makes it easier to debug when all mutations come though known methods and you can set a breakpoint in it.
This actually brings an important point to mind - any config modifications must go through its part in
normalize
withinjest-config
, to e.g. resolvetestEnvironment
to an absolute path.I guess we can punt on that for now though. But if we do kick it down the line for later, I want an
jest.unstable_modifyConfig()
or some such so we can add proper config normalization later without a breaking change.