Vitest has become one of the de facto standards for frontend testing. Version 4.0 brings important improvements in snapshot testing, component testing, and parallel execution, making the testing experience smoother.
Snapshot Testing Evolution
Snapshot testing has evolved from a "controversial feature" to a "practical tool"—the key is using it correctly:
import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/vue'
import UserCard from './UserCard.vue'
describe('UserCard', () => {
it('matches snapshot for normal user', () => {
const { container } = render(UserCard, {
props: {
user: {
name: 'John',
avatar: '/avatars/john.jpg',
role: 'developer'
}
}
})
expect(container).toMatchSnapshot()
})
it('matches snapshot for admin user', () => {
const { container } = render(UserCard, {
props: {
user: {
name: 'Jane',
avatar: '/avatars/jane.jpg',
role: 'admin'
}
}
})
expect(container).toMatchSnapshot()
})
})
Snapshot Testing Best Practices
- Small snapshots first: Only snapshot key UI fragments, not entire pages
- Meaningful snapshot names: Use
toMatchSnapshot({ customSnapshotIdentifiers })to add context - Regular review: Don't blindly accept all snapshot updates
// Good practice: small, meaningful snapshots
it('renders loading state correctly', () => {
const { container } = render(Button, { props: { loading: true } })
expect(container.querySelector('.loading-spinner')).toMatchSnapshot()
})
// Bad practice: large, vague snapshots
it('renders correctly', () => {
const { container } = render(ComplexPage)
expect(container).toMatchSnapshot() // Snapshot too large to maintain
})
Component Testing Strategy
Test Layer Division
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import LoginForm from './LoginForm.vue'
describe('LoginForm', () => {
// Layer 1: pure rendering test
it('renders all form fields', () => {
const wrapper = mount(LoginForm)
expect(wrapper.find('input[name="email"]').exists()).toBe(true)
expect(wrapper.find('input[name="password"]').exists()).toBe(true)
expect(wrapper.find('button[type="submit"]').exists()).toBe(true)
})
// Layer 2: interaction test
it('emits submit event with form data', async () => {
const wrapper = mount(LoginForm)
await wrapper.find('input[name="email"]').setValue('test@example.com')
await wrapper.find('input[name="password"]').setValue('password123')
await wrapper.find('form').trigger('submit')
expect(wrapper.emitted('submit')).toHaveLength(1)
expect(wrapper.emitted('submit')[0]).toEqual([{
email: 'test@example.com',
password: 'password123'
}])
})
// Layer 3: validation test
it('shows validation error for invalid email', async () => {
const wrapper = mount(LoginForm)
await wrapper.find('input[name="email"]').setValue('invalid-email')
await wrapper.find('form').trigger('submit')
expect(wrapper.find('.error-message').text()).toContain('Invalid email format')
})
})
Parallel Testing Optimization
Vitest 4.0's parallel testing performance has been significantly improved:
// vitest.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
// Parallel testing configuration
threads: true,
maxThreads: 4,
minThreads: 2,
// Test file isolation
isolate: true,
// Test timeout
testTimeout: 10000,
hookTimeout: 10000,
// Retry mechanism
retry: 2
}
})
Parallel Testing Considerations
- Avoid shared state: Each test should be independent, not relying on other tests' state
- Use beforeEach for cleanup: Reset state before each test
- Control parallelism reasonably: Too much parallelism may cause resource contention
describe('Database Tests', () => {
let db
beforeEach(async () => {
// Create new database connection before each test
db = await createTestDatabase()
})
afterEach(async () => {
// Clean up after each test
await db.cleanup()
})
it('creates user', async () => {
const user = await db.createUser({ name: 'John' })
expect(user.id).toBeDefined()
})
it('finds user by id', async () => {
const created = await db.createUser({ name: 'Jane' })
const found = await db.findUser(created.id)
expect(found.name).toBe('Jane')
})
})
Test Coverage Strategy
// vitest.config.ts
export default defineConfig({
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: [
'node_modules/',
'src/types/',
'*.config.*'
],
thresholds: {
statements: 80,
branches: 75,
functions: 80,
lines: 80
}
}
}
})
Summary
Vitest 4.0 makes frontend testing faster and more reliable. The key to snapshot testing is "small and precise," component testing is "clearly layered," and parallel testing is "isolated state." In real projects, reasonable testing strategy is more important than pursuing 100% coverage—testing is to verify behavior, not to satisfy numerical indicators.
