Unit Testing
Guide to unit testing DocBits components with Vitest.
Overview
All DocBits components are thoroughly tested with:
- 100+ unit tests covering all functionality
- 40+ component tests for integration scenarios
- 30+ test files with comprehensive coverage
- Full edge case handling documented in tests
Testing Setup
Installation
bash
npm install -D vitest @vue/test-utils jsdomConfiguration
typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
test: {
globals: true,
environment: 'jsdom',
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html']
}
}
})Testing Pattern: Props Validation
Test that components accept and validate props correctly:
typescript
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import ColumnOrderBy from '@/components/Table/ColumnOrderBy.vue'
describe('ColumnOrderBy - Props Validation', () => {
it('accepts required props', () => {
const wrapper = mount(ColumnOrderBy, {
props: {
column: 'name',
currentSort: null,
currentDirection: null
}
})
expect(wrapper.exists()).toBe(true)
})
it('handles string column prop', () => {
const wrapper = mount(ColumnOrderBy, {
props: {
column: 'id',
currentSort: null,
currentDirection: null
}
})
expect(wrapper.props('column')).toBe('id')
})
it('handles null currentSort', () => {
const wrapper = mount(ColumnOrderBy, {
props: {
column: 'name',
currentSort: null,
currentDirection: null
}
})
expect(wrapper.props('currentSort')).toBeNull()
})
})Testing Pattern: Event Emission
Test that components emit correct events with proper payloads:
typescript
describe('ColumnOrderBy - Event Emission', () => {
it('emits sort event on arrow click', async () => {
const wrapper = mount(ColumnOrderBy, {
props: {
column: 'name',
currentSort: null,
currentDirection: null
}
})
const upArrow = wrapper.find('.up-arrow')
await upArrow.trigger('click')
expect(wrapper.emitted('sort')).toBeTruthy()
expect(wrapper.emitted('sort')[0]).toEqual([
{ column: 'name', direction: 'asc' }
])
})
it('emits with correct direction', async () => {
const wrapper = mount(ColumnOrderBy, {
props: {
column: 'name',
currentSort: null,
currentDirection: null
}
})
const downArrow = wrapper.find('.down-arrow')
await downArrow.trigger('click')
const emitted = wrapper.emitted('sort')[0][0] as any
expect(emitted.direction).toBe('desc')
})
})Testing Pattern: User Interactions
Test component behavior with user inputs:
typescript
describe('RowSelectionCheckbox - User Interaction', () => {
it('toggles selection on click', async () => {
const wrapper = mount(RowSelectionCheckbox, {
props: {
rowId: '1',
isSelected: false
}
})
const checkbox = wrapper.find('input')
await checkbox.trigger('click')
expect(wrapper.emitted('toggle')).toBeTruthy()
expect(wrapper.emitted('toggle')[0]).toEqual(['1', true])
})
it('shows as checked when isSelected=true', () => {
const wrapper = mount(RowSelectionCheckbox, {
props: {
rowId: '1',
isSelected: true
}
})
const checkbox = wrapper.find('input')
expect(checkbox.element).toBeChecked()
})
it('shows as unchecked when isSelected=false', () => {
const wrapper = mount(RowSelectionCheckbox, {
props: {
rowId: '1',
isSelected: false
}
})
const checkbox = wrapper.find('input')
expect(checkbox.element).not.toBeChecked()
})
})Testing Pattern: Computed Properties
Test computed properties and reactive state:
typescript
describe('RowSelectionCheckbox - Computed Properties', () => {
it('shows indeterminate state correctly', () => {
const wrapper = mount(RowSelectionCheckbox, {
props: {
rowId: 'master',
isMaster: true,
isSelected: true,
totalRows: 10,
selectedCount: 5 // Partially selected
}
})
const checkbox = wrapper.find('input')
expect(checkbox.element).toHaveProperty('indeterminate', true)
})
it('generates correct ARIA label', () => {
const wrapper = mount(RowSelectionCheckbox, {
props: {
rowId: 'abc123',
isSelected: false
}
})
expect(wrapper.attributes('aria-label')).toContain('abc123')
})
})Testing Pattern: Edge Cases
Test boundary conditions and unusual inputs:
typescript
describe('ColumnOrderBy - Edge Cases', () => {
it('handles empty column name', () => {
const wrapper = mount(ColumnOrderBy, {
props: {
column: '',
currentSort: null,
currentDirection: null
}
})
expect(wrapper.exists()).toBe(true)
})
it('handles rapid clicks', async () => {
const wrapper = mount(ColumnOrderBy, {
props: {
column: 'name',
currentSort: null,
currentDirection: null
}
})
const arrow = wrapper.find('.up-arrow')
// Rapid clicks
await arrow.trigger('click')
await arrow.trigger('click')
await arrow.trigger('click')
// Should only emit when changing state
expect(wrapper.emitted('sort')).toBeTruthy()
})
it('handles same arrow click twice', async () => {
const wrapper = mount(ColumnOrderBy, {
props: {
column: 'name',
currentSort: 'name',
currentDirection: 'asc'
}
})
const sameArrow = wrapper.find('.up-arrow.selected')
await sameArrow.trigger('click')
// Should not emit when clicking already selected arrow
expect(wrapper.emitted('sort')).toBeFalsy()
})
})Running Tests
Run All Tests
bash
npm run testRun Tests with Coverage
bash
npm run test:coverageRun Tests in Watch Mode
bash
npm run test:watchRun Specific Test File
bash
npm run test -- src/components/Table/ColumnOrderBy.spec.tsRun Tests Matching Pattern
bash
npm run test -- --grep "ColumnOrderBy"Coverage Requirements
All DocBits components maintain:
- Line Coverage: >95%
- Branch Coverage: >90%
- Function Coverage: >95%
Best Practices
1. Test User Behavior, Not Implementation
typescript
// ✅ Good: Tests what user sees
it('shows sorted arrow in blue', () => {
const wrapper = mount(ColumnOrderBy, {
props: {
column: 'name',
currentSort: 'name',
currentDirection: 'asc'
}
})
const arrow = wrapper.find('.up-arrow.selected')
expect(arrow.classes()).toContain('selected')
})
// ❌ Bad: Tests internal implementation
it('sets selected property to true', () => {
const wrapper = mount(ColumnOrderBy, {
props: { ... }
})
expect(wrapper.vm.selectedArrow === 'asc').toBe(true)
})2. Test Edge Cases
Always test boundary conditions:
typescript
// Empty data
const emptyRows = []
// Single item
const singleRow = [{ id: 1 }]
// Large dataset
const largeDataset = Array(10000).fill().map((_, i) => ({ id: i }))
// Special characters
const specialChars = { name: '<script>alert("xss")</script>' }3. Use Descriptive Test Names
typescript
// ✅ Good: Clear description
it('should emit sort event with correct direction when down arrow clicked', () => { ... })
// ❌ Bad: Vague description
it('should work correctly', () => { ... })4. Arrange-Act-Assert Pattern
typescript
it('handles row selection', () => {
// Arrange: Set up the component
const wrapper = mount(RowSelectionCheckbox, {
props: { rowId: '1', isSelected: false }
})
// Act: Perform user action
const checkbox = wrapper.find('input')
await checkbox.trigger('click')
// Assert: Verify result
expect(wrapper.emitted('toggle')).toBeTruthy()
})Debugging Tests
View Component HTML
typescript
it('renders correctly', () => {
const wrapper = mount(Component, { props: { ... } })
console.log(wrapper.html()) // Print rendered HTML
})Check Event Emissions
typescript
it('emits events', async () => {
const wrapper = mount(Component, { props: { ... } })
await wrapper.find('button').trigger('click')
console.log(wrapper.emitted()) // Print all emitted events
})Watch Component Updates
typescript
it('updates state', async () => {
const wrapper = mount(Component, { props: { ... } })
console.log(wrapper.vm.$data) // Component state before
await wrapper.find('button').trigger('click')
console.log(wrapper.vm.$data) // Component state after
})Related Resources
- Component Tests - Component integration tests
- Examples - Real test examples
- Vitest Docs - Official Vitest documentation
- Vue Test Utils - Vue component testing