[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
12

Configure Feed

Select the types of activity you want to include in your feed.

feat: allow configuring expect options in the config (#5729)

authored by

Vladimir and committed by
GitHub
(May 31, 2024, 3:09 PM +0200) fc53f563 ddb09eb1

+177 -9
+35
docs/config/index.md
··· 2287 2287 - **Type:** `Partial<NodeJS.ProcessEnv>` 2288 2288 2289 2289 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
+3
docs/guide/cli-table.md
··· 114 114 | `--slowTestThreshold <threshold>` | Threshold in milliseconds for a test to be considered slow (default: `300`) | 115 115 | `--teardownTimeout <timeout>` | Default timeout of a teardown function in milliseconds (default: `10000`) | 116 116 | `--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`) | 117 120 | `--run` | Disable watch mode | 118 121 | `--no-color` | Removes colors from the console output | 119 122 | `--clearScreen` | Clear terminal screen when re-running tests during watch mode (default: `true`) |
+18
test/core/test/cli-test.test.ts
··· 316 316 expect(getCLIOptions('--merge-reports different-folder')).toEqual({ mergeReports: 'different-folder' }) 317 317 }) 318 318 319 + test('configure expect', () => { 320 + expect(() => getCLIOptions('vitest --expect.poll=1000')).toThrowErrorMatchingInlineSnapshot(`[Error: Unexpected value for --expect.poll: true. If you need to configure timeout, use --expect.poll.timeout=<timeout>]`) 321 + expect(() => getCLIOptions('vitest --expect=1000')).toThrowErrorMatchingInlineSnapshot(`[Error: Unexpected value for --expect: true. If you need to configure expect options, use --expect.{name}=<value> syntax]`) 322 + expect(getCLIOptions('vitest --expect.poll.interval=100 --expect.poll.timeout=300')).toEqual({ 323 + expect: { 324 + poll: { 325 + interval: 100, 326 + timeout: 300, 327 + }, 328 + }, 329 + }) 330 + expect(getCLIOptions('vitest --expect.requireAssertions')).toEqual({ 331 + expect: { 332 + requireAssertions: true, 333 + }, 334 + }) 335 + }) 336 + 319 337 test('public parseCLI works correctly', () => { 320 338 expect(parseCLI('vitest dev')).toEqual({ 321 339 filter: [],
+1 -1
test/core/test/web-worker-node.test.ts
··· 263 263 worker.port.close() 264 264 await new Promise((resolve) => { 265 265 worker.port.addEventListener('message', () => { 266 - expect.fail('should not trigger message') 266 + expect.unreachable('should not trigger message') 267 267 }) 268 268 worker.port.postMessage('event') 269 269 setTimeout(resolve, 100)
+2
packages/vitest/src/node/config.ts
··· 179 179 throw new Error(`You cannot set "coverage.reportsDirectory" as ${reportsDirectory}. Vitest needs to be able to remove this directory before test run`) 180 180 } 181 181 182 + resolved.expect ??= {} 183 + 182 184 resolved.deps ??= {} 183 185 resolved.deps.moduleDirectories ??= [] 184 186 resolved.deps.moduleDirectories = resolved.deps.moduleDirectories.map((dir) => {
+26
packages/vitest/src/types/config.ts
··· 707 707 } 708 708 709 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 + 734 + /** 710 735 * Modify default Chai config. Vitest uses Chai for `expect` and `assert` matches. 711 736 * https://github.com/chaijs/chai/blob/4.x.x/lib/chai/config.js 712 737 */ ··· 974 999 | 'restoreMocks' 975 1000 | 'fakeTimers' 976 1001 | 'maxConcurrency' 1002 + | 'expect' 977 1003 > & { 978 1004 sequence?: { 979 1005 concurrent?: boolean
+4 -1
test/cli/fixtures/fails/concurrent-suite-deadlock.test.ts
··· 1 1 import { createDefer } from '@vitest/utils' 2 - import { describe, test, vi } from 'vitest' 2 + import { describe, test, vi, expect } from 'vitest' 3 3 4 4 // 3 tests depend on each other, 5 5 // so they will deadlock when maxConcurrency < 3 ··· 21 21 22 22 describe('1st suite', () => { 23 23 test('a', async () => { 24 + expect(1).toBe(1) 24 25 defers[0].resolve() 25 26 await defers[2] 26 27 }) 27 28 28 29 test('b', async () => { 30 + expect(1).toBe(1) 29 31 await defers[0] 30 32 defers[1].resolve() 31 33 await defers[2] ··· 34 36 35 37 describe('2nd suite', () => { 36 38 test('c', async () => { 39 + expect(1).toBe(1) 37 40 await defers[1] 38 41 defers[2].resolve() 39 42 })
+4 -1
test/cli/fixtures/fails/concurrent-test-deadlock.test.ts
··· 1 - import { describe, test, vi } from 'vitest' 1 + import { describe, expect, test, vi } from 'vitest' 2 2 import { createDefer } from '@vitest/utils' 3 3 4 4 // 3 tests depend on each other, ··· 20 20 ] 21 21 22 22 test('a', async () => { 23 + expect(1).toBe(1) 23 24 defers[0].resolve() 24 25 await defers[2] 25 26 }) 26 27 27 28 test('b', async () => { 29 + expect(1).toBe(1) 28 30 await defers[0] 29 31 defers[1].resolve() 30 32 await defers[2] 31 33 }) 32 34 33 35 test('c', async () => { 36 + expect(1).toBe(1) 34 37 await defers[1] 35 38 defers[2].resolve() 36 39 })
+3
test/cli/fixtures/fails/no-assertions.test.ts
··· 1 + import { it } from 'vitest' 2 + 3 + it('test without assertions')
+3 -3
test/cli/fixtures/fails/test-timeout.test.ts
··· 4 4 await new Promise(resolve => setTimeout(resolve, 1000)) 5 5 }, 10) 6 6 7 - suite('suite timeout', () => { 7 + suite('suite timeout', { 8 + timeout: 100, 9 + }, () => { 8 10 test('hi', async () => { 9 11 await new Promise(resolve => setTimeout(resolve, 500)) 10 12 }) 11 - }, { 12 - timeout: 100, 13 13 }) 14 14 15 15 suite('suite timeout simple input', () => {
+2 -1
test/cli/fixtures/fails/unhandled.test.ts
··· 1 1 // @vitest-environment jsdom 2 2 3 - import { test } from 'vitest' 3 + import { expect, test } from 'vitest' 4 4 5 5 test('unhandled exception', () => { 6 + expect(1).toBe(1) 6 7 addEventListener('custom', () => { 7 8 throw new Error('some error') 8 9 })
+3
test/cli/fixtures/fails/vite.config.ts
··· 8 8 isolate: false, 9 9 }, 10 10 }, 11 + expect: { 12 + requireAssertions: true, 13 + } 11 14 }, 12 15 })
+5
test/cli/fixtures/stacktraces/require-assertions.test.js
··· 1 + import { test } from 'vitest' 2 + 3 + test('assertion is not called', () => { 4 + // no expect 5 + })
+3
test/cli/fixtures/stacktraces/vite.config.ts
··· 45 45 pool: 'forks', 46 46 include: ['**/*.{test,spec}.{imba,?(c|m)[jt]s?(x)}'], 47 47 setupFiles: ['./setup.js'], 48 + expect: { 49 + requireAssertions: true, 50 + }, 48 51 }, 49 52 })
+2
test/cli/test/__snapshots__/fails.test.ts.snap
··· 35 35 36 36 exports[`should fail nested-suite.test.ts > nested-suite.test.ts 1`] = `"AssertionError: expected true to be false // Object.is equality"`; 37 37 38 + exports[`should fail no-assertions.test.ts > no-assertions.test.ts 1`] = `"Error: expected any number of assertion, but got none"`; 39 + 38 40 exports[`should fail primitive-error.test.ts > primitive-error.test.ts 1`] = `"Unknown Error: 42"`; 39 41 40 42 exports[`should fail snapshot-with-not.test.ts > snapshot-with-not.test.ts 1`] = `
+11
test/cli/test/__snapshots__/stacktraces.test.ts.snap
··· 158 158 " 159 159 `; 160 160 161 + exports[`stacktraces should respect sourcemaps > require-assertions.test.js > require-assertions.test.js 1`] = ` 162 + " ❯ require-assertions.test.js:3:1 163 + 1| import { test } from 'vitest' 164 + 2| 165 + 3| test('assertion is not called', () => { 166 + | ^ 167 + 4| // no expect 168 + 5| }) 169 + " 170 + `; 171 + 161 172 exports[`stacktraces should respect sourcemaps > reset-modules.test.ts > reset-modules.test.ts 1`] = ` 162 173 " ❯ reset-modules.test.ts:16:26 163 174 14| expect(2 + 1).eq(3)
+8 -1
packages/vitest/src/integrations/chai/poll.ts
··· 1 1 import * as chai from 'chai' 2 2 import type { ExpectStatic } from '@vitest/expect' 3 3 import { getSafeTimers } from '@vitest/utils' 4 + import { getWorkerState } from '../../utils' 4 5 5 6 // these matchers are not supported because they don't make sense with poll 6 7 const unsupported = [ ··· 26 27 27 28 export function createExpectPoll(expect: ExpectStatic): ExpectStatic['poll'] { 28 29 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 30 37 // @ts-expect-error private poll access 31 38 const assertion = expect(null, message).withContext({ poll: true }) as Assertion 32 39 const proxy: any = new Proxy(assertion, {
+33
packages/vitest/src/node/cli/cli-config.ts
··· 585 585 description: 'Maximum number of concurrent tests in a suite (default: `5`)', 586 586 argument: '<number>', 587 587 }, 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 + }, 588 621 589 622 // CLI only options 590 623 run: {
+7
packages/vitest/src/runtime/runners/test.ts
··· 15 15 private __vitest_executor!: VitestExecutor 16 16 private cancelRun = false 17 17 18 + private assertionsErrors = new WeakMap<Readonly<Task>, Error>() 19 + 18 20 constructor(public config: ResolvedConfig) {} 19 21 20 22 importFile(filepath: string, source: VitestRunnerImportSource): unknown { ··· 123 125 throw expectedAssertionsNumberErrorGen!() 124 126 if (isExpectingAssertions === true && assertionCalls === 0) 125 127 throw isExpectingAssertionsError 128 + if (this.config.expect.requireAssertions && assertionCalls === 0) 129 + throw this.assertionsErrors.get(test) 126 130 } 127 131 128 132 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')) 129 136 let _expect: ExpectStatic | undefined 130 137 Object.defineProperty(context, 'expect', { 131 138 get() {
+4 -1
test/cli/fixtures/fails/test-extend/fixture-error.test.ts
··· 10 10 // eslint-disable-next-line unused-imports/no-unused-vars 11 11 beforeEach<{ a: never }>(({ a }) => {}) 12 12 13 - myTest('error is handled', () => {}) 13 + myTest('error is handled', () => { 14 + expect(1).toBe(1) 15 + }) 14 16 }) 15 17 16 18 describe('error thrown in afterEach fixtures', () => { ··· 24 26 afterEach<{ a: never }>(({ a }) => {}) 25 27 26 28 myTest('fixture errors', () => { 29 + expect(1).toBe(1) 27 30 expectTypeOf(1).toEqualTypeOf<number>() 28 31 }) 29 32 })