home / skills / eva813 / vue3-skills / vue-vite-testing

vue-vite-testing skill

/skills/vue-vite-testing

npx playbooks add skill eva813/vue3-skills --skill vue-vite-testing

Review the files below or copy the command above to add this skill to your agents.

Files (4)
skill.md
10.7 KB
---
name: vue-vite-testing
description: Comprehensive unit testing guide for Vue 3 + Vite projects using Vitest and Vue Test Utils. Use when writing or reviewing unit tests for Vue components, composables, Pinia stores, or TypeScript/JavaScript utilities in Vite-based projects. Covers test structure, best practices, mocking strategies, and Vue-specific testing patterns.
---

# Vue + Vite Unit Testing

## Overview

Generate comprehensive, production-ready unit tests for Vue 3 + Vite projects using Vitest framework. Follow industry best practices for testing Vue components, composables, Pinia stores, and TypeScript utilities with proper isolation, mocking, and edge case coverage.

## Testing Framework Setup

**Primary Stack:**
- **Vitest**: Fast unit test framework built for Vite
- **Vue Test Utils**: Official testing utility library for Vue components
- **@vitest/ui**: Optional UI for test visualization

**Import pattern:**
```typescript
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { mount, shallowMount } from '@vue/test-utils';
```

## Testing Workflow

Follow this systematic approach for all testing tasks:

### 1. Code Analysis Phase

**Before writing any tests:**
- Analyze the code structure and identify all public interfaces
- Identify external dependencies (APIs, stores, composables, modules)
- Note all possible code paths, conditions, and edge cases
- Ask clarifying questions about:
  - Missing type definitions or constants
  - Unclear business logic or validation rules
  - External API contracts or data structures
  - Expected error handling behaviors

**Only proceed to writing tests after full code understanding.**

### 2. Test Design Phase

**Plan test coverage:**
- Happy path scenarios (expected inputs and outputs)
- Error handling and failure modes
- Edge cases (empty arrays, null values, boundary conditions)
- Async operations (loading, success, error states)
- User interactions (clicks, inputs, form submissions)
- Lifecycle hooks and reactivity

**For Vue components, identify:**
- Props validation and default values
- Emitted events and their payloads
- Slots usage and content projection
- Computed properties and watchers
- Component lifecycle behavior

**For composables, identify:**
- Input parameters and return values
- State management and reactivity
- Side effects (API calls, localStorage, timers)
- Cleanup requirements

## Test Structure Standards

### Standard Test Template

```typescript
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';

describe('ModuleName or ComponentName', () => {
  // Top-level test variables
  let mockDependency: MockType;
  
  beforeEach(() => {
    // Reset state before each test
    mockDependency = createMockDependency();
  });

  afterEach(() => {
    // Cleanup after each test
    vi.clearAllMocks();
  });

  describe('method or feature name', () => {
    it('should handle happy path scenario', () => {
      // Arrange: Set up test data and mocks
      const input = { /* test data */ };
      
      // Act: Execute the code under test
      const result = functionUnderTest(input);
      
      // Assert: Verify expected outcomes
      expect(result).toBe(expectedValue);
    });

    it('should handle error case', async () => {
      // Arrange
      mockDependency.method.mockRejectedValue(new Error('test error'));
      
      // Act & Assert
      await expect(functionUnderTest()).rejects.toThrow('test error');
    });

    it('should handle edge case: empty input', () => {
      // Test edge cases
      expect(functionUnderTest([])).toEqual([]);
    });
  });
});
```

### AAA Pattern (Arrange-Act-Assert)

**Always structure individual tests using AAA:**

```typescript
it('should calculate total price correctly', () => {
  // Arrange: Set up test data
  const items = [
    { price: 100, quantity: 2 },
    { price: 50, quantity: 1 }
  ];
  
  // Act: Execute the function
  const total = calculateTotal(items);
  
  // Assert: Verify the result
  expect(total).toBe(250);
});
```

## Vue-Specific Testing Patterns

### 1. Component Testing

**Decide between mount vs shallowMount:**
- Use `mount()` for integration testing with child components
- Use `shallowMount()` for isolated unit testing (stubs child components)

```typescript
import { mount } from '@vue/test-utils';
import MyComponent from './MyComponent.vue';

describe('MyComponent', () => {
  it('should render with props', () => {
    const wrapper = mount(MyComponent, {
      props: {
        title: 'Test Title',
        count: 5
      }
    });
    
    expect(wrapper.find('h1').text()).toBe('Test Title');
    expect(wrapper.find('.count').text()).toBe('5');
  });

  it('should emit event on button click', async () => {
    const wrapper = mount(MyComponent);
    
    await wrapper.find('button').trigger('click');
    
    expect(wrapper.emitted('submit')).toBeTruthy();
    expect(wrapper.emitted('submit')[0]).toEqual([{ data: 'value' }]);
  });

  it('should handle v-model binding', async () => {
    const wrapper = mount(MyComponent, {
      props: {
        modelValue: 'initial'
      }
    });
    
    await wrapper.find('input').setValue('updated');
    
    expect(wrapper.emitted('update:modelValue')[0]).toEqual(['updated']);
  });
});
```

**See `references/component-testing.md` for complete component testing patterns including slots, provide/inject, and async components.**

### 2. Composables Testing

```typescript
import { composableUnderTest } from './useFeature';

describe('useFeature composable', () => {
  it('should initialize with default state', () => {
    const { state, count } = composableUnderTest();
    
    expect(state.value).toBe('idle');
    expect(count.value).toBe(0);
  });

  it('should update reactive state', () => {
    const { increment, count } = composableUnderTest();
    
    increment();
    
    expect(count.value).toBe(1);
  });

  it('should handle async operations', async () => {
    const { fetchData, data, loading } = composableUnderTest();
    
    expect(loading.value).toBe(false);
    
    const promise = fetchData();
    expect(loading.value).toBe(true);
    
    await promise;
    expect(loading.value).toBe(false);
    expect(data.value).toBeDefined();
  });
});
```

**See `references/composables-testing.md` for advanced composable testing patterns including side effects and cleanup.**

### 3. Pinia Store Testing

```typescript
import { setActivePinia, createPinia } from 'pinia';
import { useMyStore } from './myStore';

describe('myStore', () => {
  beforeEach(() => {
    setActivePinia(createPinia());
  });

  it('should initialize with default state', () => {
    const store = useMyStore();
    
    expect(store.items).toEqual([]);
    expect(store.loading).toBe(false);
  });

  it('should add item to store', () => {
    const store = useMyStore();
    const newItem = { id: 1, name: 'Test' };
    
    store.addItem(newItem);
    
    expect(store.items).toContainEqual(newItem);
  });

  it('should handle async actions', async () => {
    const store = useMyStore();
    
    await store.fetchItems();
    
    expect(store.loading).toBe(false);
    expect(store.items.length).toBeGreaterThan(0);
  });
});
```

**See `references/store-testing.md` for Pinia store testing patterns including getters, mutations, and actions.**

## Mocking Strategies

### External Dependencies

```typescript
// Mock API calls
vi.mock('@/api/users', () => ({
  fetchUsers: vi.fn(),
  createUser: vi.fn()
}));

// Mock composables
vi.mock('@/composables/useAuth', () => ({
  useAuth: vi.fn(() => ({
    user: { id: 1, name: 'Test User' },
    isAuthenticated: true,
    login: vi.fn(),
    logout: vi.fn()
  }))
}));

// Mock Vue Router
const mockRouter = {
  push: vi.fn(),
  replace: vi.fn()
};

const wrapper = mount(Component, {
  global: {
    mocks: {
      $router: mockRouter
    }
  }
});
```

### Timers and Delays

```typescript
import { vi } from 'vitest';

describe('setTimeout behavior', () => {
  beforeEach(() => {
    vi.useFakeTimers();
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  it('should execute callback after delay', () => {
    const callback = vi.fn();
    
    setTimeout(callback, 1000);
    
    expect(callback).not.toHaveBeenCalled();
    
    vi.advanceTimersByTime(1000);
    
    expect(callback).toHaveBeenCalledOnce();
  });
});
```

## Testing Best Practices

### 1. Test Isolation
- Each test should be independent and not rely on other tests
- Use `beforeEach` to reset state
- Clean up mocks with `vi.clearAllMocks()` or `vi.resetAllMocks()`

### 2. Descriptive Test Names
```typescript
// ✅ Good: Clear and descriptive
it('should display error message when API request fails', () => {});

// ❌ Bad: Vague and unclear
it('should work', () => {});
```

### 3. Avoid Test Logic
```typescript
// ❌ Bad: Contains loops and conditions
it('should validate all items', () => {
  for (const item of items) {
    if (item.type === 'special') {
      expect(validate(item)).toBe(true);
    }
  }
});

// ✅ Good: Simple and direct
it('should validate special item', () => {
  const specialItem = { type: 'special', value: 100 };
  expect(validate(specialItem)).toBe(true);
});

it('should validate normal item', () => {
  const normalItem = { type: 'normal', value: 50 };
  expect(validate(normalItem)).toBe(true);
});
```

### 4. Test Coverage Priority
1. **Critical business logic** (payment, authentication, data validation)
2. **Complex algorithms** (calculations, transformations)
3. **Error handling** (edge cases, failure modes)
4. **User interactions** (forms, buttons, navigation)
5. **Integration points** (API calls, external services)

### 5. Async Testing
```typescript
// ✅ Properly handle async operations
it('should fetch data successfully', async () => {
  const result = await fetchData();
  expect(result).toBeDefined();
});

// ✅ Use resolves/rejects for promises
await expect(fetchData()).resolves.toEqual(expectedData);
await expect(failingOperation()).rejects.toThrow('Error message');
```

## Complete Test Deliverables

When generating tests, always provide:

1. **Complete test suites** - No placeholders or "// TODO" comments
2. **All edge cases covered** - Empty inputs, null values, boundaries
3. **Proper imports** - All necessary test utilities and dependencies
4. **Appropriate mocks** - For external dependencies and side effects
5. **Clear test descriptions** - Self-documenting test names
6. **Proper cleanup** - afterEach hooks where needed

## Reference Files

For detailed examples and advanced patterns:

- **`references/component-testing.md`** - Comprehensive component testing patterns (slots, teleport, provide/inject, async components)
- **`references/composables-testing.md`** - Advanced composable testing (side effects, watchers, cleanup)
- **`references/store-testing.md`** - Pinia store testing patterns (getters, actions, state management)