- 
          
 - 
                Notifications
    
You must be signed in to change notification settings  - Fork 6.6k
 
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
mergeConfigor 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
globalConfigwas 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
normalizewithinjest-config, to e.g. resolvetestEnvironmentto 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.