toThrow matcher for exception testing
The toThrow matcher verifies that a function throws an error. You need to wrap the call in another function so that Vitest can catch the error instead of letting it crash the test. For example, expect(() => compileCode('')).toThrow().
toThrow with error message matching
The toThrow matcher can check the error message as a string or with a regex. For example, expect(() => compileCode('')).toThrow('Cannot compile empty string') or expect(() => compileCode('')).toThrow(/empty string/).
When to use vi.defineHelper
Use vi.defineHelper whenever a reusable assertion helper calls expect more than once. This applies to domain-specific helpers like expectValidJWT or any block of expect calls that would otherwise be inlined into every test. A failure in any of the inner expect calls is reported against the helper function's call site in the test.
vi.defineHelper wraps assertion helpers to fix stack traces
vi.defineHelper is a Vitest function that wraps custom assertion helper functions so that when an assertion fails, the stack trace points to the test line that called the helper rather than the line inside the helper function itself. This makes debugging easier by identifying the actual call site of the failing assertion instead of the helper's internals.
vi.defineHelper single assertion example
import { expect, test, vi } from 'vitest'
const assertPair = vi.defineHelper((a: unknown, b: unknown) => {
expect(a).toEqual(b) // ❌ failure does NOT point here
})
test('example', () => {
assertPair('left', 'right') // ✅ failure points here
})
vi.defineHelper multiple assertions example
import { expect, test, vi } from 'vitest'
const expectValidUser = vi.defineHelper((user: unknown) => {
expect(user).toHaveProperty('id')
expect(user).toHaveProperty('email')
expect(user.email).toMatch(/@/)
})
test('returns a valid user', async () => {
const user = await fetchUser('alice')
expectValidUser(user)
})
schemaMatching for mock call assertions
expect.schemaMatching is useful for asserting that a mock was called with data conforming to a schema, without spelling out every field. It is especially useful for validating the format of generated fields like UUIDs or timestamps without predicting exact values.
expect.schemaMatching asymmetric matcher
expect.schemaMatching is an asymmetric matcher introduced in Vitest 4.0.0 that takes any Standard Schema v1 object and passes if the value conforms to it. It can be composed inside any equality check the same way as expect.any or expect.stringMatching.
Mock call assertion with schemaMatching example
Example showing how to verify a mock was called with data conforming to a schema:
```ts
import { expect, test, vi } from 'vitest'
import { z } from 'zod'
const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
createdAt: z.date(),
})
test('persists a valid user', () => {
const repo = { save: vi.fn() }
registerUser(repo, { email: 'a@b.com' })
expect(repo.save).toHaveBeenCalledWith(expect.schemaMatching(UserSchema))
})
```
schemaMatching works with Zod, Valibot, and ArkType
expect.schemaMatching is compatible with any Standard Schema v1 library, including Zod, Valibot, and ArkType. The matcher validates that a value conforms to schemas defined with these libraries.
schemaMatching composable in equality checks
expect.schemaMatching can be composed inside toEqual, toStrictEqual, toMatchObject, toContainEqual, toThrow, toHaveBeenCalledWith, toHaveReturnedWith, and toHaveBeenResolvedWith assertions.
Schema-driven assertions with schemaMatching example
Example showing how to use expect.schemaMatching with Zod:
```ts
import { expect, test } from 'vitest'
import { z } from 'zod'
test('email validation', () => {
const user = { email: 'john@example.com' }
expect(user).toEqual({
email: expect.schemaMatching(z.string().email()),
})
})
```
Why toBeTruthy doesn't narrow types
expect(x).toBeTruthy() and expect(x).toBeDefined() throw at runtime when the value is missing but do not narrow the TypeScript type. They don't narrow because their TypeScript signature returns void rather than the special 'asserts' form that expect.assert uses.
Type narrowing pattern with Array.find()
When using Array.find() which returns T | undefined, use expect.assert(job) to throw if undefined and narrow the type from Job | undefined to Job for subsequent operations.
Type narrowing pattern with Map.get()
When retrieving values from a Map that returns T | undefined, use expect.assert(user) after cache.get() to throw if undefined and narrow the type so subsequent accesses do not require non-null assertions.
expect.assert for type narrowing
expect.assert() throws at runtime and narrows the TypeScript type. It replaces unsafe casts with 'as', non-null assertions with '!', or misleading runtime checks like expect(x).toBeTruthy(). The same call serves both runtime checking and type narrowing purposes.
expect.assert with boolean expressions
expect.assert() accepts any boolean expression and applies the same narrowing TypeScript would do for an if branch. This covers typeof and instanceof checks, allowing type narrowing based on conditional logic.
expect.assert pre-built helpers from chai
Pre-built helper functions are available via the expect.assert namespace: isDefined(value) narrows away undefined, isString(value) narrows to string, and instanceOf(value, Constructor) narrows to the Constructor type.
toThrow with empty string matches any error message
In Vitest 5.0, toThrow() and toThrowError() now treat an empty string argument as a substring match like any other substring. An empty string is contained in every message, so it matches any thrown error. In Vitest 4, an empty string was special-cased to /^$/ to match only empty error messages. To assert an empty message, use the pattern /^$/ explicitly.
Assertion types expose return and received types in Vitest 5.0
In Vitest 5.0, assertion interfaces use two type parameters: R is the matcher return type (void for sync, Promise<void> for async) and T is the received value type. Custom matchers should augment Matchers<R, T> interface. References to Assertion types now require both parameters: Assertion<void, string> for sync or Assertion<Promise<void>, string> for async. Vitest no longer reads declarations from global jest.Matchers interface.
expect.poll rejects when it times out
In Vitest 5.0, expect.poll() now rejects when its callback or the polled assertion does not settle within timeout. Previously, a callback resolving after the deadline could still succeed. The callback now receives an AbortSignal that aborts when timeout elapses, allowing cancellation of in-flight work. It fails with 'expect.poll() function didn't resolve in time.' or 'expect.poll() assertion didn't resolve in time.'