Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Vue 3 testing best practices with Vitest, Vue Test Utils, component testing, mocking, and Playwright E2E.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
reference/testing-no-snapshot-only.md
1---2title: Avoid Snapshot-Only Tests - They Don't Prove Correctness3impact: MEDIUM4impactDescription: Snapshot tests verify structure but not functionality, leading to false confidence and brittle tests5type: best-practice6tags: [vue3, testing, snapshot, vitest, vue-test-utils, anti-pattern]7---89# Avoid Snapshot-Only Tests - They Don't Prove Correctness1011**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.1213Use snapshots sparingly for regression detection. Prefer behavioral assertions that test what the component does.1415## Task Checklist1617- [ ] Don't use snapshots as the only assertion for component behavior18- [ ] Use snapshots for regression detection on stable UI components19- [ ] Always pair snapshots with behavioral assertions20- [ ] Keep snapshots small and focused (avoid full component snapshots)21- [ ] Review snapshot diffs carefully - don't blindly update22- [ ] Consider inline snapshots for small, critical structures2324**Incorrect:**25```javascript26import { mount } from '@vue/test-utils'27import UserCard from './UserCard.vue'2829// BAD: Snapshot-only test proves nothing about functionality30test('UserCard renders correctly', () => {31const wrapper = mount(UserCard, {32props: { user: { name: 'John', email: '[email protected]' } }33})3435expect(wrapper.html()).toMatchSnapshot()36})3738// This test passes even if:39// - The email isn't clickable40// - The avatar doesn't load41// - User actions are completely broken42// - Accessibility is broken43```4445**Correct:**46```javascript47import { mount } from '@vue/test-utils'48import UserCard from './UserCard.vue'4950// CORRECT: Test actual behavior51test('UserCard displays user information', () => {52const wrapper = mount(UserCard, {53props: { user: { name: 'John', email: '[email protected]' } }54})5556expect(wrapper.find('[data-testid="user-name"]').text()).toBe('John')57expect(wrapper.find('[data-testid="user-email"]').text()).toBe('[email protected]')58})5960test('UserCard email link is clickable', async () => {61const wrapper = mount(UserCard, {62props: { user: { name: 'John', email: '[email protected]' } }63})6465const emailLink = wrapper.find('a[href^="mailto:"]')66expect(emailLink.exists()).toBe(true)67expect(emailLink.attributes('href')).toBe('mailto:[email protected]')68})6970test('UserCard emits select event when clicked', async () => {71const wrapper = mount(UserCard, {72props: { user: { id: 1, name: 'John' } }73})7475await wrapper.trigger('click')7677expect(wrapper.emitted('select')).toBeTruthy()78expect(wrapper.emitted('select')[0]).toEqual([{ id: 1, name: 'John' }])79})80```8182## When Snapshots ARE Useful8384### Regression Detection for Stable Components85```javascript86// ACCEPTABLE: Snapshot as additional check, not the only check87test('ErrorBoundary renders error message', () => {88const wrapper = mount(ErrorBoundary, {89props: { error: new Error('Something went wrong') }90})9192// Primary assertions - verify behavior93expect(wrapper.find('.error-title').text()).toBe('Error')94expect(wrapper.find('.error-message').text()).toContain('Something went wrong')9596// Secondary snapshot - catches unexpected structural changes97expect(wrapper.find('.error-container').html()).toMatchSnapshot()98})99```100101### Inline Snapshots for Small Structures102```javascript103// ACCEPTABLE: Inline snapshot for small, critical structure104test('generates correct list markup', () => {105const wrapper = mount(ListItem, { props: { item: 'Test' } })106107expect(wrapper.html()).toMatchInlineSnapshot(`108"<li class="list-item">Test</li>"109`)110})111```112113### Complex SVG or Icon Output114```javascript115// ACCEPTABLE: Snapshot for complex generated content116test('renders correct chart SVG', () => {117const wrapper = mount(PieChart, {118props: { data: [30, 40, 30] }119})120121// Verify key behavior122expect(wrapper.findAll('path').length).toBe(3)123124// Snapshot for full SVG structure125expect(wrapper.find('svg').html()).toMatchSnapshot()126})127```128129## Better Alternatives to Snapshots130131### Test Specific Elements132```javascript133// Instead of snapshotting entire component134test('renders product with all required fields', () => {135const wrapper = mount(ProductCard, {136props: { product: { name: 'Widget', price: 9.99, inStock: true } }137})138139expect(wrapper.find('.product-name').text()).toBe('Widget')140expect(wrapper.find('.product-price').text()).toContain('9.99')141expect(wrapper.find('.in-stock-badge').exists()).toBe(true)142})143```144145### Test CSS Classes for Styling146```javascript147test('applies danger styling for errors', () => {148const wrapper = mount(Alert, {149props: { type: 'error', message: 'Failed!' }150})151152expect(wrapper.classes()).toContain('alert-danger')153expect(wrapper.find('.alert-icon').classes()).toContain('icon-error')154})155```156157### Use Testing Library Queries158```javascript159import { render, screen } from '@testing-library/vue'160161test('form has accessible labels', () => {162render(LoginForm)163164// Testing Library queries verify accessibility165expect(screen.getByLabelText('Email')).toBeInTheDocument()166expect(screen.getByLabelText('Password')).toBeInTheDocument()167expect(screen.getByRole('button', { name: 'Sign In' })).toBeInTheDocument()168})169```170171## Snapshot Anti-Patterns172173```javascript174// ANTI-PATTERN: Giant component snapshot175test('page renders', () => {176const wrapper = mount(EntirePageComponent)177expect(wrapper.html()).toMatchSnapshot() // 500+ lines of HTML178})179180// ANTI-PATTERN: Snapshot with dynamic content181test('shows current date', () => {182const wrapper = mount(DateDisplay)183expect(wrapper.html()).toMatchSnapshot() // Fails every day!184})185186// ANTI-PATTERN: Snapshot after every test187test('button works', async () => {188const wrapper = mount(Counter)189await wrapper.find('button').trigger('click')190expect(wrapper.html()).toMatchSnapshot() // Redundant191})192```193194## Reference195- [Vue.js Testing Guide - What Not to Test](https://vuejs.org/guide/scaling-up/testing)196- [Effective Snapshot Testing](https://kentcdodds.com/blog/effective-snapshot-testing)197- [Vitest Snapshot Testing](https://vitest.dev/guide/snapshot.html)198