Setting the file. One moment.
Skill 16 · Vue Testing Best Practices
Subchapter 16.8
reference/testing-no-snapshot-only.mdMarkdown6 KBView on GitHub
Impact: MEDIUM - Snapshot tests only verify that HTML structure hasn’t changed - they don’t verify that the component works correctly. Relying exclusively on snapshots leads to false confidence and tests that break on any refactoring, even when functionality is preserved.
Also bundled
LICENSEUse snapshots sparingly for regression detection. Prefer behavioral assertions that test what the component does.
Incorrect:
import { mount } from '@vue/test-utils'
import UserCard from './UserCard.vue'
// BAD: Snapshot-only test proves nothing about functionality
test('UserCard renders correctly', () => {
const wrapper = mount(UserCard, {
props: { user: { name: 'John', email: 'john@example.com' } }
})
expect(wrapper.html()).toMatchSnapshot()
})
// This test passes even if:
// - The email isn't clickable
// - The avatar doesn't load
// - User actions are completely broken
// - Accessibility is brokenCorrect:
import { mount } from '@vue/test-utils'
import UserCard from './UserCard.vue'
// CORRECT: Test actual behavior
test('UserCard displays user information', () => {
const wrapper = mount(UserCard, {
props: { user: { name: 'John', email: 'john@example.com' } }
})
expect(wrapper.find('[data-testid="user-name"]').text()).toBe('John')
expect(wrapper.find('[data-testid="user-email"]').text()).toBe('john@example.com')
})
test('UserCard email link is clickable', async () => {
const wrapper = mount(UserCard, {
props: { user: { name: 'John', email: 'john@example.com' } }
})
const emailLink = wrapper.find('a[href^="mailto:"]')
expect(emailLink.exists()).toBe(true)
expect(emailLink.attributes('href')).toBe('mailto:john@example.com')
})
test('UserCard emits select event when clicked', async () => {
const wrapper = mount(UserCard, {
props: { user: { id: 1, name: 'John' } }
})
await wrapper.trigger('click')
expect(wrapper.emitted('select')).toBeTruthy()
expect(wrapper.emitted('select')[0]).toEqual([{ id: 1, name: 'John' }])
})// ACCEPTABLE: Snapshot as additional check, not the only check
test('ErrorBoundary renders error message', () => {
const wrapper = mount(ErrorBoundary, {
props: { error: new Error('Something went wrong') }
})
// Primary assertions - verify behavior
expect(wrapper.find('.error-title').text()).toBe('Error')
expect(wrapper.find('.error-message').text()).toContain('Something went wrong')
// Secondary snapshot - catches unexpected structural changes
expect(wrapper.find('.error-container').html()).toMatchSnapshot()
})// ACCEPTABLE: Inline snapshot for small, critical structure
test('generates correct list markup', () => {
const wrapper = mount(ListItem, { props: { item: 'Test' } })
expect(wrapper.html()).toMatchInlineSnapshot(`
"<li class="list-item">Test</li>"
`)
})// ACCEPTABLE: Snapshot for complex generated content
test('renders correct chart SVG', () => {
const wrapper = mount(PieChart, {
props: { data: [30, 40, 30] }
})
// Verify key behavior
expect(wrapper.findAll('path').length).toBe(3)
// Snapshot for full SVG structure
expect(wrapper.find('svg').html()).toMatchSnapshot()
})// Instead of snapshotting entire component
test('renders product with all required fields', () => {
const wrapper = mount(ProductCard, {
props: { product: { name: 'Widget', price: 9.99, inStock: true } }
})
expect(wrapper.find('.product-name').text()).toBe('Widget')
expect(wrapper.find('.product-price').text()).toContain('9.99')
expect(wrapper.find('.in-stock-badge').exists()).toBe(true)
})test('applies danger styling for errors', () => {
const wrapper = mount(Alert, {
props: { type: 'error', message: 'Failed!' }
})
expect(wrapper.classes()).toContain('alert-danger')
expect(wrapper.find('.alert-icon').classes()).toContain('icon-error')
})import { render, screen } from '@testing-library/vue'
test('form has accessible labels', () => {
render(LoginForm)
// Testing Library queries verify accessibility
expect(screen.getByLabelText('Email')).toBeInTheDocument()
expect(screen.getByLabelText('Password')).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Sign In' })).toBeInTheDocument()
})// ANTI-PATTERN: Giant component snapshot
test('page renders', () => {
const wrapper = mount(EntirePageComponent)
expect(wrapper.html()).toMatchSnapshot() // 500+ lines of HTML
})
// ANTI-PATTERN: Snapshot with dynamic content
test('shows current date', () => {
const wrapper = mount(DateDisplay)
expect(wrapper.html()).toMatchSnapshot() // Fails every day!
})
// ANTI-PATTERN: Snapshot after every test
test('button works', async () => {
const wrapper = mount(Counter)
await wrapper.find('button').trigger('click')
expect(wrapper.html()).toMatchSnapshot() // Redundant
})