Skip to content
Closed
Show file tree
Hide file tree
Changes from 9 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]` Allow enabling Jest global types through `"types": ["jest"]` in `tsconfig.json` ([#12856](https://github.com/facebook/jest/pull/12856))
- `[@jest/reporters]` Improve `GitHubActionsReporter`s annotation format ([#12826](https://github.com/facebook/jest/pull/12826))

### Fixes
Expand Down
124 changes: 99 additions & 25 deletions docs/ExpectAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ When you're writing tests, you often need to check that values meet certain cond

For additional Jest matchers maintained by the Jest Community check out [`jest-extended`](https://github.com/jest-community/jest-extended).

:::info

The TypeScript examples from this page assume you are using type definitions from [`jest`](GettingStarted.md/#type-definitions).

:::

## Methods

import TOCInline from '@theme/TOCInline';
Expand All @@ -17,6 +23,9 @@ import TOCInline from '@theme/TOCInline';

## Reference

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

### `expect(value)`

The `expect` function is used every time you want to test a value. You will rarely call `expect` by itself. Instead, you will use `expect` along with a "matcher" function to assert something about a value.
Expand All @@ -37,56 +46,121 @@ The argument to `expect` should be the value that your code produces, and any ar

You can use `expect.extend` to add your own matchers to Jest. For example, let's say that you're testing a number utility library and you're frequently asserting that numbers appear within particular ranges of other numbers. You could abstract that into a `toBeWithinRange` matcher:

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

```js
const toBeWithinRange = function (actual, floor, ceiling) {
if (
typeof actual !== 'number' ||
typeof floor !== 'number' ||
typeof ceiling !== 'number'
) {
throw new Error('These must be of type number!');
}

const pass = actual >= floor && actual <= ceiling;
if (pass) {
return {
message: () =>
`expected ${this.utils.printReceived(
actual,
)} not to be within range ${this.utils.printExpected(
`${floor} - ${ceiling}`,
)}`,
pass: true,
};
} else {
return {
message: () =>
`expected ${this.utils.printReceived(
actual,
)} to be within range ${this.utils.printExpected(
`${floor} - ${ceiling}`,
)}`,
pass: false,
};
}
};

expect.extend({
toBeWithinRange(received, floor, ceiling) {
const pass = received >= floor && received <= ceiling;
toBeWithinRange,
});
```

</TabItem>

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

```ts
import type {MatcherFunction} from 'expect';

const toBeWithinRange: MatcherFunction<[floor: number, ceiling: number]> =
function (actual: unknown, floor: unknown, ceiling: unknown) {
if (
typeof actual !== 'number' ||
typeof floor !== 'number' ||
typeof ceiling !== 'number'
) {
throw new Error('These must be of type number!');
}

const pass = actual >= floor && actual <= ceiling;
if (pass) {
return {
message: () =>
`expected ${received} not to be within range ${floor} - ${ceiling}`,
`expected ${this.utils.printReceived(
actual,
)} not to be within range ${this.utils.printExpected(
`${floor} - ${ceiling}`,
)}`,
pass: true,
};
} else {
return {
message: () =>
`expected ${received} to be within range ${floor} - ${ceiling}`,
`expected ${this.utils.printReceived(
actual,
)} to be within range ${this.utils.printExpected(
`${floor} - ${ceiling}`,
)}`,
pass: false,
};
}
},
};

expect.extend({
toBeWithinRange,
});

declare module 'expect' {
interface AsymmetricMatchers {
toBeWithinRange(floor: number, ceiling: number): void;
}
interface Matchers<R> {
toBeWithinRange(floor: number, ceiling: number): R;
}
}
```

</TabItem>
</Tabs>

And use it in a test:

```js
test('numeric ranges', () => {
expect(100).toBeWithinRange(90, 110);

expect(101).not.toBeWithinRange(0, 100);

expect({apples: 6, bananas: 3}).toEqual({
apples: expect.toBeWithinRange(1, 10),
bananas: expect.not.toBeWithinRange(11, 20),
});
});
```

:::note

In TypeScript, when using `@types/jest` for example, you can declare the new `toBeWithinRange` matcher in the imported module like this:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This page can have more TS examples, of course. I just wanted to remove this old pain.


```ts
interface CustomMatchers<R = unknown> {
toBeWithinRange(floor: number, ceiling: number): R;
}

declare global {
namespace jest {
interface Expect extends CustomMatchers {}
interface Matchers<R> extends CustomMatchers<R> {}
interface InverseAsymmetricMatchers extends CustomMatchers {}
}
}
```

:::

#### Async Matchers

`expect.extend` also supports async matchers. Async matchers return a Promise so you will need to await the returned value. Let's use an example matcher to illustrate the usage of them. We are going to implement a matcher called `toBeDivisibleByExternalValue`, where the divisible number is going to be pulled from an external source.
Expand Down
29 changes: 25 additions & 4 deletions docs/GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,31 @@ npm install --save-dev ts-jest

#### Type definitions

You may also want to install the [`@types/jest`](https://www.npmjs.com/package/@types/jest) module for the version of Jest you're using. This will help provide full typing when writing your tests with TypeScript.
To enable type definitions of [Jest globals](GlobalAPI.md) add `"jest"` to the `"types"` list of your `tsconfig.json`:

> For `@types/*` modules it's recommended to try to match the version of the associated module. For example, if you are using `26.4.0` of `jest` then using `26.4.x` of `@types/jest` is ideal. In general, try to match the major (`26`) and minor (`4`) version as closely as possible.
```json title="tsconfig.json"
{
"compilerOptions": {
"types": ["jest"]
}
}
```

```bash npm2yarn
npm install --save-dev @types/jest
Alternatively you may use explicit imports from `@jest/globals` package:

```ts title="sum.test.ts"
import {describe, expect, test} from '@jest/globals';
import {sum} from './sum';

describe('sum module', () => {
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
});
```

:::info

If you had `@types/jest` installed in your project before, remember to remove it.

:::
8 changes: 2 additions & 6 deletions docs/MockFunctionAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@ Mock functions are also known as "spies", because they let you spy on the behavi

:::info

The TypeScript examples from this page will only work as document if you import `jest` from `'@jest/globals'`:

```ts
import {jest} from '@jest/globals';
```
The TypeScript examples from this page assume you are using type definitions from [`jest`](GettingStarted.md/#type-definitions).

:::

Expand Down Expand Up @@ -178,7 +174,7 @@ mockFn(3); // 39

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

```js
```ts
const mockFn = jest.fn((scalar: number) => 42 + scalar);

mockFn(0); // 42
Expand Down
19 changes: 14 additions & 5 deletions docs/UpgradingToJest28.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,16 +189,25 @@ Known examples of packages that fails in Jest 28 are [`uuid`](https://npmjs.com/

## TypeScript

:::info
From Jest 28.2 you can replace `@types/jest` package with native type definitions. Simply add `"jest"` to the `"types"` list of your `tsconfig.json` and enjoy typed testing!

```diff title="tsconfig.json"
{
"compilerOptions": {
- "types": ["@types/jest"]
+ "types": ["jest"]
}
}
```

The TypeScript examples from this page will only work as document if you import `jest` from `'@jest/globals'`:
:::info

```ts
import {jest} from '@jest/globals';
```
If you had `@types/jest` installed in your project before, remember to remove it.

:::

The TypeScript examples bellow assume you are using type definitions from `jest`.

### `jest.fn()`

`jest.fn()` now takes only one generic type argument. See [Mock Functions API](MockFunctionAPI.md) page for more usage examples.
Expand Down
4 changes: 3 additions & 1 deletion e2e/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
"moduleResolution": "node",
"isolatedModules": true,
"importsNotUsedAsValues": "error",
"resolveJsonModule": true
"resolveJsonModule": true,

"types": ["jest"]
}
}
1 change: 0 additions & 1 deletion examples/angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
"@babel/plugin-proposal-decorators": "*",
"@babel/preset-env": "^7.1.0",
"@babel/preset-typescript": "^7.0.0",
"@types/jest": "^27.4.0",
"babel-jest": "workspace:*",
"babel-plugin-transform-typescript-metadata": "*",
"jest": "workspace:*",
Expand Down
1 change: 0 additions & 1 deletion examples/expect-extend/__tests__/ranges.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/

import {expect, test} from '@jest/globals';
import '../toBeWithinRange';

test('is within range', () => expect(100).toBeWithinRange(90, 110));
Expand Down
1 change: 0 additions & 1 deletion examples/expect-extend/toBeWithinRange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/

import {expect} from '@jest/globals';
import type {MatcherFunction} from 'expect';

const toBeWithinRange: MatcherFunction<[floor: number, ceiling: number]> =
Expand Down
3 changes: 2 additions & 1 deletion examples/expect-extend/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"compilerOptions": {
"strict": true
"strict": true,
"types": ["jest"]
},
"include": ["./**/*"]
}
1 change: 0 additions & 1 deletion examples/typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
"@babel/preset-env": "^7.1.0",
"@babel/preset-react": "^7.12.1",
"@babel/preset-typescript": "^7.0.0",
"@types/jest": "^27.4.0",
"babel-jest": "workspace:*",
"jest": "workspace:*"
},
Expand Down
3 changes: 2 additions & 1 deletion examples/typescript/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"compilerOptions": {
"module": "commonjs",
"jsx": "react-jsx"
"jsx": "react-jsx",
"types": ["jest"]
}
}
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
"@types/babel__generator": "^7.0.0",
"@types/babel__template": "^7.0.2",
"@types/dedent": "^0.7.0",
"@types/jest": "^27.4.0",
"@types/node": "~12.12.0",
"@types/which": "^2.0.0",
"@typescript-eslint/eslint-plugin": "^5.14.0",
Expand Down
10 changes: 10 additions & 0 deletions packages/babel-jest/src/__tests__/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
Copy link
Contributor Author

@mrazauskas mrazauskas May 17, 2022

Choose a reason for hiding this comment

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

__tests__ and e2e directories needs additional tsconfig.json with "types": ["jest"]. That’s a lot of new files. For now I keep them all the same hoping this would help to review the PR. In few test suites some type fixing here and there is needed. Not touching these now. I will come back with fix PRs case-by-case.

Also here I have to extend the root tsconfig.json, because tsconfig.json in the enclosing directory is excluding __tests__. Seems like excludes can not be overridden.

"extends": "../../../../tsconfig.json",
"compilerOptions": {
"composite": false,
"esModuleInterop": true,
Copy link
Member

Choose a reason for hiding this comment

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

do we need this? (applies to all)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Some tests needs it. For example, commenting out "esModuleInterop": true give type error here:

https://github.com/facebook/jest/blob/ae8cf9a78cb0cd2f54d3bacbc1c8837e540e21dc/packages/pretty-format/src/__tests__/react.test.tsx#L8

My idea was to use the same tsconfig.json file for all packages to simplify review. But I can go through and enable it case by case. This option applies only for files inside __tests__ directories. Perhaps it is fine to keep it?

Copy link
Member

Choose a reason for hiding this comment

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

A separate PR making it unneeded would be awesome, but shouldn't clutter up this pr

"rootDir": "../",
"types": ["jest"]
},
"include": ["../**/*"]
}
10 changes: 10 additions & 0 deletions packages/babel-plugin-jest-hoist/src/__tests__/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "../../../../tsconfig.json",
"compilerOptions": {
"composite": false,
"esModuleInterop": true,
"rootDir": "../",
"types": ["jest"]
},
"include": ["../**/*"]
}
10 changes: 10 additions & 0 deletions packages/diff-sequences/src/__tests__/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "../../../../tsconfig.json",
"compilerOptions": {
"composite": false,
"esModuleInterop": true,
"rootDir": "../",
"types": ["jest"]
},
"include": ["../**/*"]
}
10 changes: 10 additions & 0 deletions packages/expect-utils/src/__tests__/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "../../../../tsconfig.json",
"compilerOptions": {
"composite": false,
"esModuleInterop": true,
"rootDir": "../",
"types": ["jest"]
},
"include": ["../**/*"]
}
10 changes: 10 additions & 0 deletions packages/expect/src/__tests__/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "../../../../tsconfig.json",
"compilerOptions": {
"composite": false,
"esModuleInterop": true,
"rootDir": "../",
"types": ["jest"]
},
"include": ["../**/*"]
}
Loading