Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### Features

- `[jest, jest-config]` Add `defineConfig()` type helper and expose `JestConfig` type ([#12801](https://github.com/facebook/jest/pull/12801))
- `[@jest/reporters]` Improve `GitHubActionsReporter`s annotation format ([#12826](https://github.com/facebook/jest/pull/12826))

### Fixes
Expand Down
102 changes: 67 additions & 35 deletions docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,78 +3,110 @@ id: configuration
title: Configuring Jest
---

Jest's configuration can be defined in the `package.json` file of your project, or through a `jest.config.js`, or `jest.config.ts` file or through the `--config <path/to/file.js|ts|cjs|mjs|json>` option. If you'd like to use your `package.json` to store Jest's config, the `"jest"` key should be used on the top level so Jest will know how to find your settings:
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

```json
{
"name": "my-project",
"jest": {
"verbose": true
}
}
```
The Jest philosophy is to work great by default, but sometimes you just need more configuration power.

It is recommended to define the configuration in a dedicated JavaScript, TypeScript or JSON file. The file will be discovered automatically, if it is named `jest.config.js|ts|cjs|mjs|json`. You can use [`--config`](CLI.md#--configpath) flag to pass an explicit path to the file.

:::note

Keep in mind that the resulting configuration object must always be JSON-serializable.

:::

Or through JavaScript:
The configuration file should simply export an object or a function returning an object:

```js title="jest.config.js"
// Sync object
/** @type {import('@jest/types').Config.InitialOptions} */
const config = {
module.exports = {
verbose: true,
};

module.exports = config;

// Or async function
// or async function
module.exports = async () => {
return {
verbose: true,
};
};
```

Or through TypeScript (if `ts-node` is installed):
Optionally you can use `defineConfig()` helper for type definitions and autocompletion:

<Tabs groupId="examples">
<TabItem value="js" label="JavaScript">

```js title="jest.config.js"
const {defineConfig} = require('jest');

module.exports = defineConfig({
verbose: true,
});

// or async function
module.exports = defineConfig(async () => {
const verbose = await asyncGetVerbose();

return {verbose};
});
```

</TabItem>

<TabItem value="ts" label="TypeScript">

```ts title="jest.config.ts"
import type {Config} from '@jest/types';
import {JestConfig, defineConfig} from 'jest';

// Sync object
const config: Config.InitialOptions = {
export default defineConfig({
verbose: true,
};
export default config;
});

// Or async function
export default async (): Promise<Config.InitialOptions> => {
return {
verbose: true,
};
};
// or async function
export default defineConfig(async () => {
const verbose: JestConfig['verbose'] = await asyncGetVerbose();

return {verbose};
});
```

Please keep in mind that the resulting configuration must be JSON-serializable.
</TabItem>
</Tabs>

:::tip

When using the `--config` option, the JSON file must not contain a "jest" key:
Jest requires [`ts-node`](https://github.com/TypeStrong/ts-node) to be able to read TypeScript configuration files. Make sure it is installed in your project.

```json
:::

The configuration can be stored in a JSON file as a plain object:

```json title="jest.config.json"
{
"bail": 1,
"verbose": true
}
```

## Options
Alternatively Jest's configuration can be defined through the `"jest"` key in the `package.json` of your project:

These options let you control Jest's behavior in your `package.json` file. The Jest philosophy is to work great by default, but sometimes you just need more configuration power.
```json title="package.json"
{
"name": "my-project",
"jest": {
"verbose": true
}
}
```

### Defaults
## Options

You can retrieve Jest's default options to expand them if needed:

```js title="jest.config.js"
const {defaults} = require('jest-config');
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still thinking. Perhaps it would be better to expose defineJestConfig(), JestConfig and jestDefaults from 'jest'? Or better from 'jest-config'? It is nice to install only jest, in the other hand these are just devDependencies.


module.exports = {
// ...
moduleFileExtensions: [...defaults.moduleFileExtensions, 'ts', 'tsx'],
// ...
};
Expand Down
95 changes: 95 additions & 0 deletions e2e/__tests__/defineConfig.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import * as path from 'path';
import {onNodeVersions} from '@jest/test-utils';
import {cleanup, writeFiles} from '../Utils';
import {getConfig} from '../runJest';

const DIR = path.resolve(__dirname, '../define-config');

beforeEach(() => cleanup(DIR));
afterAll(() => cleanup(DIR));

test('works with object config exported from CJS file', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));",
'jest.config.js': `
const {defineConfig} = require('jest');
module.exports = defineConfig({displayName: 'cjs-object-config', verbose: true});
`,
'package.json': '{}',
});

const {configs, globalConfig} = getConfig(path.join(DIR));

expect(configs).toHaveLength(1);
expect(configs[0].displayName?.name).toBe('cjs-object-config');
expect(globalConfig.verbose).toBe(true);
});

test('works with function config exported from CJS file', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));",
'jest.config.js': `
const {defineConfig} = require('jest');
async function getVerbose() {return true;}
module.exports = defineConfig(async () => {
const verbose = await getVerbose();
return {displayName: 'cjs-async-function-config', verbose };
});
`,
'package.json': '{}',
});

const {configs, globalConfig} = getConfig(path.join(DIR));

expect(configs).toHaveLength(1);
expect(configs[0].displayName?.name).toBe('cjs-async-function-config');
expect(globalConfig.verbose).toBe(true);
});

// The versions where vm.Module exists and commonjs with "exports" is not broken
onNodeVersions('>=12.16.0', () => {
test('works with object config exported from ESM file', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));",
'jest.config.js': `
import jest from 'jest';
export default jest.defineConfig({displayName: 'esm-object-config', verbose: true});
`,
'package.json': '{"type": "module"}',
});

const {configs, globalConfig} = getConfig(path.join(DIR));

expect(configs).toHaveLength(1);
expect(configs[0].displayName?.name).toBe('esm-object-config');
expect(globalConfig.verbose).toBe(true);
});

test('works with function config exported from ESM file', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));",
'jest.config.js': `
import jest from 'jest';
async function getVerbose() {return true;}
export default jest.defineConfig(async () => {
const verbose = await getVerbose();
return {displayName: 'esm-async-function-config', verbose};
});
`,
'package.json': '{"type": "module"}',
});

const {configs, globalConfig} = getConfig(path.join(DIR));

expect(configs).toHaveLength(1);
expect(configs[0].displayName?.name).toBe('esm-async-function-config');
expect(globalConfig.verbose).toBe(true);
});
});
127 changes: 127 additions & 0 deletions e2e/__tests__/tsIntegration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,130 @@ describe('when `Config` type is imported from "@jest/types"', () => {
});
});
});

describe('when `defineConfig` is imported from "jest"', () => {
test('with object config exported from TS file', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));",
'jest.config.ts': `
import {defineConfig} from 'jest';
export default defineConfig({
displayName: 'ts-object-config',
verbose: true,
});
`,
'package.json': '{}',
});

const {configs, globalConfig} = getConfig(path.join(DIR));

expect(configs).toHaveLength(1);
expect(configs[0].displayName?.name).toBe('ts-object-config');
expect(globalConfig.verbose).toBe(true);
});

test('with function config exported from TS file', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));",
'jest.config.ts': `
import {JestConfig, defineConfig} from 'jest';
async function getVerbose() {return true;}
export default defineConfig(async () => {
const verbose: JestConfig['verbose'] = await getVerbose();
return {displayName: 'ts-async-function-config', verbose};
});
`,
'package.json': '{}',
});

const {configs, globalConfig} = getConfig(path.join(DIR));

expect(configs).toHaveLength(1);
expect(configs[0].displayName?.name).toBe('ts-async-function-config');
expect(globalConfig.verbose).toBe(true);
});

test('throws if type errors are encountered', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));",
'jest.config.ts': `
import {defineConfig} from 'jest';
export default defineConfig({
fakeTimers: 'all'
});
`,
'package.json': '{}',
});

const {stderr, exitCode} = runJest(DIR);

expect(stderr).toMatch(
"jest.config.ts(3,3): error TS2322: Type 'string' is not assignable to type 'FakeTimers | undefined'.",
);
expect(exitCode).toBe(1);
});

// The versions where vm.Module exists and commonjs with "exports" is not broken
onNodeVersions('>=12.16.0', () => {
test('works with object config exported from TS file when package.json#type=module', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));",
'jest.config.ts': `
import {defineConfig} from 'jest';
export default defineConfig({
displayName: 'ts-esm-object-config',
verbose: true,
});
`,
'package.json': '{"type": "module"}',
});

const {configs, globalConfig} = getConfig(path.join(DIR));

expect(configs).toHaveLength(1);
expect(configs[0].displayName?.name).toBe('ts-esm-object-config');
expect(globalConfig.verbose).toBe(true);
});

test('works with function config exported from TS file when package.json#type=module', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));",
'jest.config.ts': `
import {JestConfig, defineConfig} from 'jest';
async function getVerbose() {return true;}
export default defineConfig(async () => {
const verbose: JestConfig['verbose'] = await getVerbose();
return {displayName: 'ts-esm-async-function-config', verbose};
});
`,
'package.json': '{"type": "module"}',
});

const {configs, globalConfig} = getConfig(path.join(DIR));

expect(configs).toHaveLength(1);
expect(configs[0].displayName?.name).toBe('ts-esm-async-function-config');
expect(globalConfig.verbose).toBe(true);
});

test('throws if type errors are encountered when package.json#type=module', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));",
'jest.config.ts': `
import {defineConfig} from 'jest';
export default defineConfig({
fakeTimers: 'all'
});
`,
'package.json': '{"type": "module"}',
});

const {stderr, exitCode} = runJest(DIR);

expect(stderr).toMatch(
"jest.config.ts(3,3): error TS2322: Type 'string' is not assignable to type 'FakeTimers | undefined'.",
);
expect(exitCode).toBe(1);
});
});
});
Loading