遷移指南
升級至 Vitest 3.0
測試選項作為第三個參數
如果您將物件作為第三個參數傳遞給 test
或 describe
函數,Vitest 3.0 會發出警告:
test('validation works', () => {
// ...
}, { retry: 3 });
test('validation works', { retry: 3 }, () => {
// ...
});
如果第三個參數是物件,下一個主要版本將會拋出錯誤。請注意,逾時時間並未棄用:
test('validation works', () => {
// ...
}, 1000); // Ok ✅
browser.name
和 browser.providerOptions
已棄用
browser.name
和 browser.providerOptions
都將在 Vitest 4 中移除。請改用新的 browser.instances
選項:
export default defineConfig({
test: {
browser: {
name: 'chromium',
providerOptions: {
launch: { devtools: true },
},
instances: [
{
browser: 'chromium',
launch: { devtools: true },
},
],
},
},
})
透過新的 browser.instances
欄位,您還可以指定多個瀏覽器配置。
spy.mockReset
現在會還原原始實作
以前,沒有好的方法可以在不重新套用 spy 的情況下將 spy 重置為原始實作。現在,spy.mockReset
會將實作函數重置為原始函數,而不是一個假的 noop。
const foo = {
bar: () => 'Hello, world!',
};
vi.spyOn(foo, 'bar').mockImplementation(() => 'Hello, mock!');
foo.bar(); // 'Hello, mock!'
foo.bar.mockReset();
foo.bar(); // undefined
foo.bar(); // 'Hello, world!'
vi.spyOn
如果方法已被模擬,則會重複使用模擬
以前,Vitest 在監控物件時總是會分配一個新的 spy。這導致 mockRestore
出錯,因為它會將 spy 還原到先前的 spy,而不是原始函數:
vi.spyOn(fooService, 'foo').mockImplementation(() => 'bar');
vi.spyOn(fooService, 'foo').mockImplementation(() => 'bar');
vi.restoreAllMocks();
vi.isMockFunction(fooService.foo); // true
vi.isMockFunction(fooService.foo); // false
假計時器預設值
Vitest 不再提供預設的 fakeTimers.toFake
選項。現在,Vitest 會模擬任何可用的計時器相關 API(除了 nextTick
)。具體來說,當呼叫 vi.useFakeTimers
時,performance.now()
現在會被模擬。
vi.useFakeTimers();
performance.now(); // original
performance.now(); // fake
您可以透過在呼叫 vi.useFakeTimers
時或在配置中全域指定計時器來恢復先前的行為:
export default defineConfig({
test: {
fakeTimers: {
toFake: [
'setTimeout',
'clearTimeout',
'setInterval',
'clearInterval',
'setImmediate',
'clearImmediate',
'Date',
]
},
},
})
更嚴格的錯誤相等性
Vitest 現在在透過 toEqual
或 toThrowError
比較錯誤時會檢查更多屬性。Vitest 現在會比較 name
、message
、cause
和 AggregateError.errors
。對於 Error.cause
,比較是以非對稱方式進行的:
expect(new Error('hi', { cause: 'x' })).toEqual(new Error('hi')); // ✅
expect(new Error('hi')).toEqual(new Error('hi', { cause: 'x' })); // ❌
除了檢查更多屬性之外,Vitest 現在還會比較錯誤原型。例如,如果拋出 TypeError
,相等性檢查應該參考 TypeError
,而不是 Error
:
expect(() => {
throw new TypeError('type error');
})
.toThrowError(new Error('type error'))
.toThrowError(new TypeError('type error'));
有關更多詳細資訊,請參閱 PR:#5876。
Vite 6 預設不解析 module
條件匯出
Vite 6 允許更靈活的 resolve.conditions
選項,Vitest 預設將其配置為排除 module
條件匯出。 有關 Vite 端變更的詳細資訊,請參閱 Vite 6 遷移指南。
Custom
類型已棄用 API
Custom
類型現在是 Test
類型的別名。請注意,Vitest 在 2.1 中更新了公開類型,並將匯出名稱更改為 RunnerCustomCase
和 RunnerTestCase
:
import {
RunnerCustomCase,
RunnerTestCase,
} from 'vitest';
如果您正在使用 getCurrentSuite().custom()
,則返回任務的 type
現在等於 'test'
。Custom
類型將在 Vitest 4 中移除。
WorkspaceSpec
類型不再使用 API
在公開 API 中,此類型以前用於自訂 sequencers。請改為遷移到 TestSpecification
。
onTestFinished
和 onTestFailed
現在接收上下文
onTestFinished
和 onTestFailed
鉤子以前接收測試結果作為第一個參數。現在,它們接收測試上下文,就像 beforeEach
和 afterEach
一樣。
快照 API 的變更 API
@vitest/snapshot
中的公開快照 API 已更改,以支援單次執行中的多個狀態。有關更多詳細資訊,請參閱 PR:#6817
請注意,此變更僅影響直接使用快照 API 的開發人員。.toMatchSnapshot
API 沒有任何變更。
resolveConfig
類型簽章的變更 API
resolveConfig
現在更有用。它不再接受已解析的 Vite 配置,而是接受使用者配置並返回已解析的配置。
此函數未在內部使用,僅作為公開 API 公開。
清理 vitest/reporters
類型 API
vitest/reporters
入口點現在只匯出報告器實作和選項類型。如果您需要存取 TestCase
/TestSuite
和其他任務相關類型,請從 vitest/node
額外匯入它們。
即使覆寫 coverage.excludes
,覆蓋率也會忽略測試檔案。
現在無法透過覆寫 coverage.excludes
將測試檔案包含在覆蓋率報告中。測試檔案現在總是會被排除。
遷移至 Vitest 2.0
預設池為 forks
Vitest 2.0 將 pool
的預設配置更改為 'forks'
,以獲得更好的穩定性。您可以在 PR 中閱讀完整的動機。
如果您在未指定 pool
的情況下使用了 poolOptions
,您可能需要更新配置:
export default defineConfig({
test: {
poolOptions: {
threads: {
singleThread: true,
},
forks: {
singleFork: true,
},
}
}
})
鉤子以堆疊方式執行
在 Vitest 2.0 之前,所有鉤子都平行執行。在 2.0 中,所有鉤子都依序執行。此外,afterAll
/afterEach
鉤子以相反順序執行。
要恢復鉤子的平行執行,請將 sequence.hooks
更改為 'parallel'
:
export default defineConfig({
test: {
sequence: {
hooks: 'parallel',
},
},
})
suite.concurrent
平行執行所有測試
以前,在套件上指定 concurrent
會按套件分組平行測試,並依序執行。現在,遵循 Jest 的行為,所有測試都平行執行(受 maxConcurrency
限制)。
V8 覆蓋率的 coverage.ignoreEmptyLines
預設啟用
coverage.ignoreEmptyLines
的預設值現在為 true。此重大變更可能會影響程式碼覆蓋率報告,需要為某些專案調整覆蓋率閾值。此調整僅影響當 coverage.provider
為 'v8'
時的預設設定。
移除 watchExclude
選項
Vitest 使用 Vite 的監控器。透過將檔案或目錄新增到 server.watch.ignored
來排除它們:
export default defineConfig({
server: {
watch: {
ignored: ['!node_modules/examplejs']
}
}
})
--segfault-retry
已移除
隨著預設池的變更,此選項不再需要。如果您遇到 segfault 錯誤,請嘗試切換到 'forks'
池。如果問題仍然存在,請開啟一個新問題並提供重現步驟。
套件任務中的空任務已移除
這是對進階 任務 API 的變更。以前,遍歷 .suite
最終會導致使用空內部套件,而不是檔案任務。
這使得 .suite
成為可選;如果任務定義在頂層,它將沒有套件。您可以回退到現在所有任務(包括檔案任務本身,因此請注意不要陷入無限遞迴)都存在的 .file
屬性。
此變更還從 expect.getState().currentTestName
中移除了檔案,並使 expect.getState().testPath
成為必需。
task.meta
已新增至 JSON 報告器
JSON 報告器現在會為每個斷言結果列印 task.meta
。
模擬函數的簡化泛型類型(例如 vi.fn<T>
、Mock<T>
)
以前 vi.fn<TArgs, TReturn>
分別接受兩個泛型類型用於參數和返回值。這已更改為直接接受函數類型 vi.fn<T>
,以簡化使用。
import { vi } from 'vitest';
import type { Mock } from 'vitest';
const add = (x: number, y: number): number => x + y;
// using vi.fn<T>
const mockAdd = vi.fn<Parameters<typeof add>, ReturnType<typeof add>>();
const mockAdd = vi.fn<typeof add>();
// using Mock<T>
const mockAdd: Mock<Parameters<typeof add>, ReturnType<typeof add>> = vi.fn();
const mockAdd: Mock<typeof add> = vi.fn();
存取已解析的 mock.results
以前,如果函數返回 Promise,Vitest 會解析 mock.results
值。現在有一個單獨的 mock.settledResults
屬性,只有當返回的 Promise 被解析或拒絕時才會填充。
const fn = vi.fn().mockResolvedValueOnce('result');
await fn();
const result = fn.mock.results[0]; // 'result'
const result = fn.mock.results[0]; // 'Promise<result>'
const settledResult = fn.mock.settledResults[0]; // 'result'
隨著此變更,我們還引入了新的 toHaveResolved*
匹配器,類似於 toHaveReturned
,以便在您以前使用 toHaveReturned
時更容易遷移:
const fn = vi.fn().mockResolvedValueOnce('result');
await fn();
expect(fn).toHaveReturned('result');
expect(fn).toHaveResolved('result');
瀏覽器模式
Vitest 瀏覽器模式在 Beta 週期中發生了許多變更。您可以在 GitHub 討論頁面 上閱讀我們關於瀏覽器模式的理念。
大多數變更都是新增的,但也有一些小的破壞性變更:
已移除的棄用選項
一些棄用選項已移除:
vitest typecheck
命令 - 請改用vitest --typecheck
VITEST_JUNIT_CLASSNAME
和VITEST_JUNIT_SUITE_NAME
環境變數(請改用報告器選項)- 檢查
c8
覆蓋率(請改用 coverage-v8) - 從
vitest
匯出SnapshotEnvironment
- 請改從vitest/snapshot
匯入 SpyInstance
已移除,改用MockInstance
遷移至 Vitest 1.0
最低要求
Vitest 1.0 需要 Vite 5.0 和 Node.js 18 或更高版本。
所有 @vitest/*
子套件都需要 Vitest 1.0 版。
快照更新 #3961
快照中的引號不再逸出,所有快照都使用反引號 (`),即使字串只有一行。
- 引號不再逸出:
expect({ foo: 'bar' }).toMatchInlineSnapshot(`
Object {
- \\"foo\\": \\"bar\\",
+ "foo": "bar",
}
`)
- 單行快照現在使用 "`" 引號而不是 ':':
- expect('some string').toMatchInlineSnapshot('"some string"')
+ expect('some string').toMatchInlineSnapshot(`"some string"`)
@vitest/snapshot
套件也有 變更。如果您沒有直接使用它,則無需更改任何內容。
- 您不再需要僅為了覆寫
equalityCheck
方法而擴展SnapshotClient
:只需在實例化時將其作為isEqual
傳遞即可 client.setTest
已重新命名為client.startCurrentRun
client.resetCurrent
已重新命名為client.finishCurrentRun
池已標準化 #4172
我們移除了許多配置選項,以便更容易根據您的需求配置執行器。如果您依賴 --threads
或其他相關旗標,請查看遷移範例。
--threads
現在是--pool=threads
--no-threads
現在是--pool=forks
--single-thread
現在是--poolOptions.threads.singleThread
--experimental-vm-threads
現在是--pool=vmThreads
--experimental-vm-worker-memory-limit
現在是--poolOptions.vmThreads.memoryLimit
--isolate
現在是--poolOptions.<pool-name>.isolate
和browser.isolate
test.maxThreads
現在是test.poolOptions.<pool-name>.maxThreads
test.minThreads
現在是test.poolOptions.<pool-name>.minThreads
test.useAtomics
現在是test.poolOptions.<pool-name>.useAtomics
test.poolMatchGlobs.child_process
現在是test.poolMatchGlobs.forks
test.poolMatchGlobs.experimentalVmThreads
現在是test.poolMatchGlobs.vmThreads
{
scripts: {
- "test": "vitest --no-threads"
// For identical behaviour:
+ "test": "vitest --pool forks --poolOptions.forks.singleFork"
// Or multi parallel forks:
+ "test": "vitest --pool forks"
}
}
{
scripts: {
- "test": "vitest --experimental-vm-threads"
+ "test": "vitest --pool vmThreads"
}
}
{
scripts: {
- "test": "vitest --isolate false"
+ "test": "vitest --poolOptions.threads.isolate false"
}
}
{
scripts: {
- "test": "vitest --no-threads --isolate false"
+ "test": "vitest --pool forks --poolOptions.forks.isolate false"
}
}
覆蓋率變更 #4265, #4442
選項 coverage.all
現在預設啟用。這表示所有符合 coverage.include
模式的專案檔案都將被處理,即使它們未執行。
覆蓋率閾值 API 的形狀已更改,現在支援使用 glob 模式為特定檔案指定閾值:
export default defineConfig({
test: {
coverage: {
- perFile: true,
- thresholdAutoUpdate: true,
- 100: true,
- lines: 100,
- functions: 100,
- branches: 100,
- statements: 100,
+ thresholds: {
+ perFile: true,
+ autoUpdate: true,
+ 100: true,
+ lines: 100,
+ functions: 100,
+ branches: 100,
+ statements: 100,
+ }
}
}
})
模擬類型 #4400
為了採用 Jest 風格的「Mock」命名,一些類型已被移除。
- import { EnhancedSpy, SpyInstance } from 'vitest'
+ import { MockInstance } from 'vitest'
WARNING
SpyInstance
已棄用,改用 MockInstance
,並將在下一個主要版本中移除。
計時器模擬 #3925
vi.useFakeTimers()
不再自動模擬 process.nextTick
。 仍然可以透過明確指定 vi.useFakeTimers({ toFake: ['nextTick'] })
來模擬 process.nextTick
。
但是,當使用 --pool=forks
時,無法模擬 process.nextTick
。如果您需要 process.nextTick
模擬,請使用不同的 --pool
選項。
從 Jest 遷移
Vitest 的設計與 Jest 相容的 API,以便盡可能簡化從 Jest 的遷移。儘管如此,您仍然可能會遇到以下差異:
預設為全域變數
Jest 預設啟用其 全域變數 API。Vitest 則不然。您可以透過 全域變數配置設定 啟用全域變數,或更新您的程式碼以使用從 vitest
模組匯入的內容。
如果您決定停用全域變數,請注意像 testing-library
這樣的常用函式庫將不會自動執行 DOM 清理。
spy.mockReset
Jest 的 mockReset
會將模擬實作替換為一個返回 undefined
的空函數。
Vitest 的 mockReset
會將模擬實作重置為其原始實作。 也就是說,重置由 vi.fn(impl)
建立的模擬會將模擬實作重置為 impl
。
模組模擬
在 Jest 中模擬模組時,工廠參數的返回值是預設匯出。在 Vitest 中,工廠參數必須返回一個明確定義每個匯出的物件。例如,以下 jest.mock
必須更新如下:
jest.mock('./some-path', () => 'hello')
vi.mock('./some-path', () => ({
default: 'hello',
}))
有關更多詳細資訊,請參閱 vi.mock
API 部分。
自動模擬行為
與 Jest 不同,<root>/__mocks__
中的模擬模組不會載入,除非呼叫 vi.mock()
。如果您需要它們在每個測試中都被模擬,就像在 Jest 中一樣,您可以在 setupFiles
中模擬它們。
匯入模擬套件的原始檔案
如果您只部分模擬套件,您可能以前使用過 Jest 的函數 requireActual
。在 Vitest 中,您應該將這些呼叫替換為 vi.importActual
。
const { cloneDeep } = jest.requireActual('lodash/cloneDeep');
const { cloneDeep } = await vi.importActual('lodash/cloneDeep');
將模擬擴展到外部函式庫
Jest 預設會這樣做,當模擬模組並希望此模擬擴展到使用相同模組的其他外部函式庫時,您應該明確指出您希望模擬哪個第三方函式庫,以便外部函式庫成為您的原始碼的一部分,方法是使用 server.deps.inline。
server.deps.inline: ["lib-name"]
expect.getState().currentTestName
Vitest 的 test
名稱以 >
符號連接,以便更容易區分測試和套件,而 Jest 使用空格 ()。
- `${describeTitle} ${testTitle}`
+ `${describeTitle} > ${testTitle}`
環境變數
就像 Jest 一樣,如果 NODE_ENV
之前未設定,Vitest 會將其設定為 test
。Vitest 也有一個 JEST_WORKER_ID
的對應項,稱為 VITEST_POOL_ID
(總是小於或等於 maxThreads
),因此如果您依賴它,請不要忘記重新命名它。Vitest 還公開了 VITEST_WORKER_ID
,它是正在執行工作者的唯一 ID - 此數字不受 maxThreads
影響,並且會隨著每個建立的工作者而增加。
替換屬性
如果您想修改物件,您將在 Jest 中使用 replaceProperty API,您也可以在 Vitest 中使用 vi.stubEnv
或 vi.spyOn
來執行相同的操作。
完成回呼
從 Vitest v0.10.0 開始,宣告測試的回呼樣式已棄用。您可以將它們重寫為使用 async
/await
函數,或使用 Promise 來模擬回呼樣式。
it('should work', (done) => {
it('should work', () => new Promise(done => {
// ...
done()
})
}))
鉤子
beforeAll
/beforeEach
鉤子在 Vitest 中可能會返回 teardown 函數。因此,如果您的鉤子返回的不是 undefined
或 null
,您可能需要重寫您的鉤子宣告:
beforeEach(() => setActivePinia(createTestingPinia()))
beforeEach(() => { setActivePinia(createTestingPinia()) })
在 Jest 中,鉤子是依序呼叫的(一個接一個)。預設情況下,Vitest 會平行執行鉤子。要使用 Jest 的行為,請更新 sequence.hooks
選項:
export default defineConfig({
test: {
sequence: {
hooks: 'list',
}
}
})
類型
Vitest 沒有等同於 jest
命名空間的內容,因此您需要直接從 vitest
匯入類型:
let fn: jest.Mock<(name: string) => number>;
import type { Mock } from 'vitest';
let fn: Mock<(name: string) => number>;
計時器
Vitest 不支援 Jest 的舊版計時器。
逾時
如果您使用 jest.setTimeout
,您需要遷移到 vi.setConfig
:
jest.setTimeout(5_000);
vi.setConfig({ testTimeout: 5_000 });
Vue 快照
這不是 Jest 特有的功能,但如果您以前使用 Jest 和 vue-cli 預設,您將需要安裝 jest-serializer-vue
套件,並在 setupFiles 中使用它:
import { defineConfig } from 'vite';
export default defineConfig({
test: {
setupFiles: ['./tests/unit/setup.js'],
},
});
import vueSnapshotSerializer from 'jest-serializer-vue';
expect.addSnapshotSerializer(vueSnapshotSerializer);
否則您的快照將會有許多逸出的 "
字元。