Skip to content

Commit fc53f56

Browse files
authored
feat: allow configuring expect options in the config (#5729)
1 parent ddb09eb commit fc53f56

File tree

20 files changed

+177
-9
lines changed

20 files changed

+177
-9
lines changed

docs/config/index.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2287,3 +2287,38 @@ If you just need to configure snapshots feature, use [`snapshotFormat`](#snapsho
22872287
- **Type:** `Partial<NodeJS.ProcessEnv>`
22882288

22892289
Environment variables available on `process.env` and `import.meta.env` during tests. These variables will not be available in the main process (in `globalSetup`, for example).
2290+
2291+
### expect
2292+
2293+
- **Type:** `ExpectOptions`
2294+
2295+
#### expect.requireAssertions
2296+
2297+
- **Type:** `boolean`
2298+
- **Default:** `false`
2299+
2300+
The same as calling [`expect.hasAssertions()`](/api/expect#expect-hasassertions) at the start of every test. This makes sure that no test will pass accidentally.
2301+
2302+
::: tip
2303+
This only works with Vitest's `expect`. If you use `assert` ot `.should` assertions, they will not count, and your test will fail due to the lack of expect assertions.
2304+
2305+
You can change the value of this by calling `vi.setConfig({ expect: { requireAssertions: false } })`. The config will be applied to every subsequent `expect` call until the `vi.resetConfig` is called manually.
2306+
:::
2307+
2308+
#### expect.poll
2309+
2310+
Global configuration options for [`expect.poll`](/api/expect#poll). These are the same options you can pass down to `expect.poll(condition, options)`.
2311+
2312+
##### expect.poll.interval
2313+
2314+
- **Type:** `number`
2315+
- **Default:** `50`
2316+
2317+
Polling interval in milliseconds
2318+
2319+
##### expect.poll.timeout
2320+
2321+
- **Type:** `number`
2322+
- **Default:** `1000`
2323+
2324+
Polling timeout in milliseconds

docs/guide/cli-table.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@
114114
| `--slowTestThreshold <threshold>` | Threshold in milliseconds for a test to be considered slow (default: `300`) |
115115
| `--teardownTimeout <timeout>` | Default timeout of a teardown function in milliseconds (default: `10000`) |
116116
| `--maxConcurrency <number>` | Maximum number of concurrent tests in a suite (default: `5`) |
117+
| `--expect.requireAssertions` | Require that all tests have at least one assertion |
118+
| `--expect.poll.interval <interval>` | Poll interval in milliseconds for `expect.poll()` assertions (default: `50`) |
119+
| `--expect.poll.timeout <timeout>` | Poll timeout in milliseconds for `expect.poll()` assertions (default: `1000`) |
117120
| `--run` | Disable watch mode |
118121
| `--no-color` | Removes colors from the console output |
119122
| `--clearScreen` | Clear terminal screen when re-running tests during watch mode (default: `true`) |

packages/vitest/src/integrations/chai/poll.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as chai from 'chai'
22
import type { ExpectStatic } from '@vitest/expect'
33
import { getSafeTimers } from '@vitest/utils'
4+
import { getWorkerState } from '../../utils'
45

56
// these matchers are not supported because they don't make sense with poll
67
const unsupported = [
@@ -26,7 +27,13 @@ const unsupported = [
2627

2728
export function createExpectPoll(expect: ExpectStatic): ExpectStatic['poll'] {
2829
return function poll(fn, options = {}) {
29-
const { interval = 50, timeout = 1000, message } = options
30+
const state = getWorkerState()
31+
const defaults = state.config.expect?.poll ?? {}
32+
const {
33+
interval = defaults.interval ?? 50,
34+
timeout = defaults.timeout ?? 1000,
35+
message,
36+
} = options
3037
// @ts-expect-error private poll access
3138
const assertion = expect(null, message).withContext({ poll: true }) as Assertion
3239
const proxy: any = new Proxy(assertion, {

packages/vitest/src/node/cli/cli-config.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,39 @@ export const cliOptionsConfig: VitestCLIOptions = {
585585
description: 'Maximum number of concurrent tests in a suite (default: `5`)',
586586
argument: '<number>',
587587
},
588+
expect: {
589+
description: 'Configuration options for `expect()` matches',
590+
argument: '', // no displayed
591+
subcommands: {
592+
requireAssertions: {
593+
description: 'Require that all tests have at least one assertion',
594+
},
595+
poll: {
596+
description: 'Default options for `expect.poll()`',
597+
argument: '',
598+
subcommands: {
599+
interval: {
600+
description: 'Poll interval in milliseconds for `expect.poll()` assertions (default: `50`)',
601+
argument: '<interval>',
602+
},
603+
timeout: {
604+
description: 'Poll timeout in milliseconds for `expect.poll()` assertions (default: `1000`)',
605+
argument: '<timeout>',
606+
},
607+
},
608+
transform(value) {
609+
if (typeof value !== 'object')
610+
throw new Error(`Unexpected value for --expect.poll: ${value}. If you need to configure timeout, use --expect.poll.timeout=<timeout>`)
611+
return value
612+
},
613+
},
614+
},
615+
transform(value) {
616+
if (typeof value !== 'object')
617+
throw new Error(`Unexpected value for --expect: ${value}. If you need to configure expect options, use --expect.{name}=<value> syntax`)
618+
return value
619+
},
620+
},
588621

589622
// CLI only options
590623
run: {

packages/vitest/src/node/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ export function resolveConfig(
179179
throw new Error(`You cannot set "coverage.reportsDirectory" as ${reportsDirectory}. Vitest needs to be able to remove this directory before test run`)
180180
}
181181

182+
resolved.expect ??= {}
183+
182184
resolved.deps ??= {}
183185
resolved.deps.moduleDirectories ??= []
184186
resolved.deps.moduleDirectories = resolved.deps.moduleDirectories.map((dir) => {

packages/vitest/src/runtime/runners/test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export class VitestTestRunner implements VitestRunner {
1515
private __vitest_executor!: VitestExecutor
1616
private cancelRun = false
1717

18+
private assertionsErrors = new WeakMap<Readonly<Task>, Error>()
19+
1820
constructor(public config: ResolvedConfig) {}
1921

2022
importFile(filepath: string, source: VitestRunnerImportSource): unknown {
@@ -123,9 +125,14 @@ export class VitestTestRunner implements VitestRunner {
123125
throw expectedAssertionsNumberErrorGen!()
124126
if (isExpectingAssertions === true && assertionCalls === 0)
125127
throw isExpectingAssertionsError
128+
if (this.config.expect.requireAssertions && assertionCalls === 0)
129+
throw this.assertionsErrors.get(test)
126130
}
127131

128132
extendTaskContext<T extends Test | Custom>(context: TaskContext<T>): ExtendedContext<T> {
133+
// create error during the test initialization so we have a nice stack trace
134+
if (this.config.expect.requireAssertions)
135+
this.assertionsErrors.set(context.task, new Error('expected any number of assertion, but got none'))
129136
let _expect: ExpectStatic | undefined
130137
Object.defineProperty(context, 'expect', {
131138
get() {

packages/vitest/src/types/config.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,31 @@ export interface InlineConfig {
706706
waitForDebugger?: boolean
707707
}
708708

709+
/**
710+
* Configuration options for expect() matches.
711+
*/
712+
expect?: {
713+
/**
714+
* Throw an error if tests don't have any expect() assertions.
715+
*/
716+
requireAssertions?: boolean
717+
/**
718+
* Default options for expect.poll()
719+
*/
720+
poll?: {
721+
/**
722+
* Timeout in milliseconds
723+
* @default 1000
724+
*/
725+
timeout?: number
726+
/**
727+
* Polling interval in milliseconds
728+
* @default 50
729+
*/
730+
interval?: number
731+
}
732+
}
733+
709734
/**
710735
* Modify default Chai config. Vitest uses Chai for `expect` and `assert` matches.
711736
* https://github.com/chaijs/chai/blob/4.x.x/lib/chai/config.js
@@ -974,6 +999,7 @@ export type RuntimeConfig = Pick<
974999
| 'restoreMocks'
9751000
| 'fakeTimers'
9761001
| 'maxConcurrency'
1002+
| 'expect'
9771003
> & {
9781004
sequence?: {
9791005
concurrent?: boolean

test/cli/fixtures/fails/concurrent-suite-deadlock.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createDefer } from '@vitest/utils'
2-
import { describe, test, vi } from 'vitest'
2+
import { describe, test, vi, expect } from 'vitest'
33

44
// 3 tests depend on each other,
55
// so they will deadlock when maxConcurrency < 3
@@ -21,11 +21,13 @@ describe('wrapper', { concurrent: true, timeout: 500 }, () => {
2121

2222
describe('1st suite', () => {
2323
test('a', async () => {
24+
expect(1).toBe(1)
2425
defers[0].resolve()
2526
await defers[2]
2627
})
2728

2829
test('b', async () => {
30+
expect(1).toBe(1)
2931
await defers[0]
3032
defers[1].resolve()
3133
await defers[2]
@@ -34,6 +36,7 @@ describe('wrapper', { concurrent: true, timeout: 500 }, () => {
3436

3537
describe('2nd suite', () => {
3638
test('c', async () => {
39+
expect(1).toBe(1)
3740
await defers[1]
3841
defers[2].resolve()
3942
})

test/cli/fixtures/fails/concurrent-test-deadlock.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, test, vi } from 'vitest'
1+
import { describe, expect, test, vi } from 'vitest'
22
import { createDefer } from '@vitest/utils'
33

44
// 3 tests depend on each other,
@@ -20,17 +20,20 @@ describe('wrapper', { concurrent: true, timeout: 500 }, () => {
2020
]
2121

2222
test('a', async () => {
23+
expect(1).toBe(1)
2324
defers[0].resolve()
2425
await defers[2]
2526
})
2627

2728
test('b', async () => {
29+
expect(1).toBe(1)
2830
await defers[0]
2931
defers[1].resolve()
3032
await defers[2]
3133
})
3234

3335
test('c', async () => {
36+
expect(1).toBe(1)
3437
await defers[1]
3538
defers[2].resolve()
3639
})
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { it } from 'vitest'
2+
3+
it('test without assertions')

0 commit comments

Comments
 (0)