DEV Community

A0mineTV
A0mineTV

Posted on

How I Tested My Vue.js Calculator with Vitest: A Complete Guide

While building my Vue 3 calculator, I discovered:

"The simpler the UI, the more dangerous the edge cases."

Real-world issues I faced:

// Floating-point math surprises 0.1 + 0.2 // → 0.30000000000000004 (not 0.3!) // State corruption memoryRecall() + 5 // → "105" (string concatenation) 
Enter fullscreen mode Exit fullscreen mode

⚡ Why Vitest Was the Perfect Fit

🏆 Key Advantages Over Jest

Feature Vitest Jest
Speed 0.3s cold start 2.1s cold start
Vue 3 Support Zero-config Needs plugins
TypeScript Native Babel required
Watch Mode Instant HMR Full re-runs
Console UI Colored diffs Basic output
npm install -D vitest @vue/test-utils happy-dom 
Enter fullscreen mode Exit fullscreen mode

🧠 Critical Decisions

1- Shared Config with Vite

No duplicate configs - uses your existing vite.config.ts:

 // vite.config.ts import { defineConfig } from 'vitest/config' export default defineConfig({ test: { environment: 'happy-dom' } }) 
Enter fullscreen mode Exit fullscreen mode

2- Component Testing Magic

Mount components with Vue-specific utils:

 import { mount } from '@vue/test-utils' const wrapper = mount(Calculator, { props: { initialValue: '0' } }) 
Enter fullscreen mode Exit fullscreen mode

3- TypeScript First

Full type inference out-of-the-box:

 test('memory add is type-safe', () => { const result = memoryAdd(2, 3) // TS checks args/return expect(result).toBeTypeOf('number') }) 
Enter fullscreen mode Exit fullscreen mode

Why It Matters: Catches integration issues between components.


📊 Results That Surprised Me

🔍 Test Coverage Report

---------------|---------|----------|---------|---------|------------------- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Lines ---------------|---------|----------|---------|---------|------------------- All files | 94.7 | 89.2 | 92.3 | 95.1 | calculator.ts | 100 | 100 | 100 | 100 | memory.ts | 92.1| 87.5 | 90.9 | 93.3 | 24-25,42 theme-switcher| 89.5 | 85.7 | 88.9 | 90.0 | 15,33 
Enter fullscreen mode Exit fullscreen mode

🎯 Unexpected Wins

1- Caught Hidden Floating-Point Bugs

 // Before 0.1 + 0.2  0.30000000000000004 // After expect(calculate(0.1, 0.2, '+')).toBeCloseTo(0.3) 
Enter fullscreen mode Exit fullscreen mode

2- Exposed State Leaks

 // Memory recall corrupted display MR  "undefined5" // Fixed: expect(memoryRecall()).toBeTypeOf('number') 
Enter fullscreen mode Exit fullscreen mode

📈 Performance Metrics

Metric Before Tests After Tests
Bug Reports 8/month 0/month
Debug Time 2.1h/issue 0.3h/issue
Refactor Speed 1x baseline 3.5x faster

🧩 Gaps Uncovered

pie title Coverage Gaps "Floating-Point Logic" : 15 "Memory Overflow" : 28 "Theme Persistence" : 57 
Enter fullscreen mode Exit fullscreen mode

🎯 Key Lessons Learned

1. Test Behavior, Not Implementation

// ❌ Fragile (breaks if button class changes) expect(wrapper.find('.btn-submit').exists()).toBe(true) // ✅ Robust (tests actual functionality) expect(wrapper.find('[data-test="submit"]').exists()).toBe(true) 
Enter fullscreen mode Exit fullscreen mode

Why it matters: Survived 3 major UI refactors without test updates.


2. The Testing Pyramid is Real

graph TD A[70% Unit Tests] -->|Fast| B(Calculator logic) B --> C(Utils) D[25% Component Tests] -->|Integration| E(Vue components) E --> F(State management) G[5% E2E Tests] -->|User Flows| H(Keyboard input) 
Enter fullscreen mode Exit fullscreen mode

Actual time savings:

  • Unit tests: 98ms avg
  • Component tests: 420ms avg
  • E2E tests: 2.1s avg

3. Mocks Should Mirror Reality

// ❌ Over-mocking vi.spyOn(console, 'error') // Masked real errors // ✅ Realistic localStorage mock const localStorageMock = (() => { let store: Record<string, string> = {} return { getItem: vi.fn((key) => store[key]), setItem: vi.fn((key, value) => { store[key] = value.toString() }), clear: vi.fn(() => { store = {} }) } })() 
Enter fullscreen mode Exit fullscreen mode

4. TypeScript is Your Testing Ally

interface TestCase { input: [number, number, Operator] expected: number | string name: string } const testCases: TestCase[] = [ { input: [5, 0, '÷'], expected: 'Error', name: 'Division by zero' }, // ...50+ cases ] test.each(testCases)('$name', ({ input, expected }) => { expect(calculate(...input)).toBe(expected) }) 
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • Auto-complete for test data
  • Compile-time error if types change
  • Self-documenting tests

5. Visual Testing Matters Too

test('theme contrast meets WCAG', async () => { await wrapper.setData({ darkMode: true }) const bg = getComputedStyle(wrapper.element).backgroundColor const text = getComputedStyle(wrapper.find('.display').element).color expect(contrastRatio(bg, text)).toBeGreaterThan(4.5) }) 
Enter fullscreen mode Exit fullscreen mode

Tool used: jest-axe for accessibility assertions.


💡 Golden Rule

"Write tests that would have caught yesterday's bugs today, and will catch tomorrow's bugs next week."


🚀 Try It Yourself

📥 1. Clone & Setup

# Clone repository git clone https://github.com/VincentCapek/calculator.git # Navigate to project cd calculator # Install dependencies npm install 
Enter fullscreen mode Exit fullscreen mode

🧪 2. Run Test Suite

# Run all tests npm test # Watch mode (development) npm run test:watch # Generate coverage report npm run test:coverage 
Enter fullscreen mode Exit fullscreen mode

🎮 3. Key Test Scripts to Explore

describe('Memory Functions', () => { it('M+ adds to memory', () => { const { memoryAdd, memory } = useCalculator() memoryAdd(5) expect(memory.value).toBe(5) }) }) 
Enter fullscreen mode Exit fullscreen mode
test('keyboard input updates display', async () => { const wrapper = mount(Calculator) await wrapper.vm.handleKeyPress({ key: '7' }) expect(wrapper.find('.display').text()).toBe('7') }) 
Enter fullscreen mode Exit fullscreen mode

🏁 Wrapping Up

Building this tested calculator taught me one core truth:

"Good tests don’t just prevent bugs—they document how your code should behave."

🔗 Explore Further

💬 Let’s Discuss

  • What’s your #1 testing challenge in Vue apps?
  • Would you like a follow-up on CI/CD integration?
  • Found a creative testing solution? Share below!

Happy testing! 🧪🚀

Top comments (0)