diff --git a/.vitepress/components/FeaturesList.vue b/.vitepress/components/FeaturesList.vue index 11c24230..82526fba 100644 --- a/.vitepress/components/FeaturesList.vue +++ b/.vitepress/components/FeaturesList.vue @@ -36,9 +36,7 @@ import ListItem from './ListItem.vue' Jest expect API - 内置 - Tinyspy 用于对象 - Mock + 兼容 Jest 对象模拟 使用 diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 3b05b7ed..507100c4 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -51,7 +51,7 @@ export default ({ mode }: { mode: string }) => { ['link', { rel: 'icon', href: '/favicon.ico', sizes: '48x48' }], ['link', { rel: 'icon', href: '/logo-without-border.svg', type: 'image/svg+xml' }], ['meta', { name: 'author', content: `${teamMembers.map(c => c.name).join(', ')} and ${vitestName} contributors` }], - ['meta', { name: 'keywords', content: 'vitest, vite, test, coverage, snapshot, react, vue, preact, svelte, solid, lit, marko, ruby, cypress, puppeteer, jsdom, happy-dom, test-runner, jest, typescript, esm, tinyspy, node' }], + ['meta', { name: 'keywords', content: 'vitest, vite, test, coverage, snapshot, react, vue, preact, svelte, solid, lit, marko, ruby, cypress, puppeteer, jsdom, happy-dom, test-runner, jest, typescript, esm, node' }], ['meta', { property: 'og:title', content: vitestName }], ['meta', { property: 'og:description', content: vitestDescription }], ['meta', { property: 'og:url', content: ogUrl }], diff --git a/api/expect.md b/api/expect.md index c611043c..1ec7ba5b 100644 --- a/api/expect.md +++ b/api/expect.md @@ -861,6 +861,52 @@ test('throws non-Error values', () => { }) ``` ::: + +:::warning Unhandled Rejections with Fake Timers +When using fake timers, an async function that rejects _during_ a `vi.advanceTimersByTimeAsync` call will trigger an [unhandled rejection](https://nodejs.org/api/process.html#event-unhandledrejection) — even if you later assert it with `.rejects.toThrow()`. This happens because the error is thrown before the `expect` chain has a chance to catch it. + +```ts +async function foo() { + await new Promise(resolve => setTimeout(resolve, 100)) + throw new Error('boom') +} + +test('rejects', async () => { + const result = foo() + + await vi.advanceTimersByTimeAsync(100) + + // The assertion passes, but the error was already "unhandled" during advanceTimersByTimeAsync + await expect(result).rejects.toThrow() +}) +``` + +To avoid this, prefer [`vi.setTimerTickMode('nextTimerAsync')`](/api/vi#vi-settimertickmode) so that timers tick automatically as promises settle, without needing a manual advance: + +```ts +beforeEach(() => { + vi.useFakeTimers() + vi.setTimerTickMode('nextTimerAsync') +}) + +test('rejects', async () => { + // No advanceTimersByTimeAsync needed — the error is caught by rejects.toThrow() + await expect(foo()).rejects.toThrow('boom') +}) +``` + +Alternatively, set up the `.rejects.toThrow()` assertion _before_ advancing timers so the rejection is handled immediately: + +```ts +test('rejects', async () => { + const result = foo() + const assertion = expect(result).rejects.toThrow('boom') + + await vi.advanceTimersByTimeAsync(100) + await assertion +}) +``` +::: ## toMatchSnapshot diff --git a/guide/features.md b/guide/features.md index 5d480c74..3dc5411d 100644 --- a/guide/features.md +++ b/guide/features.md @@ -120,7 +120,7 @@ it('renders correctly', () => { ## 对象模拟 (Mocking) {#mocking} -内置 [Tinyspy](https://github.com/tinylibs/tinyspy) 用于在 `vi` 对象上使用 `jest` 兼容的 API 进行对象模拟。 +Vitest 在 `vi` 对象上提供了与 `Jest` 兼容的 API 接口。 ```ts import { expect, vi } from 'vitest'