| name | unit-testing |
| description | Vitest testing patterns for Vue components and Express routes. Use when writing unit tests, integration tests, mocking dependencies, or testing async code. |
| when_to_use | When writing Vitest unit tests, mocking Prisma or Pinia stores, or testing Vue components with Vue Test Utils. |
Unit Testing Skill
Patterns for unit and integration testing with Vitest.
When to Use This Skill
- Writing Vue component tests
- Writing Express route tests
- Mocking Pinia stores
- Mocking Prisma database
- Testing async operations
Reference Documentation
For detailed patterns and conventions, see:
Quick Reference
Vue Component Test Pattern
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { mount, VueWrapper } from '@vue/test-utils'
import { createTestingPinia } from '@pinia/testing'
import TaskCard from '@/components/TaskCard.vue'
describe('TaskCard', () => {
let wrapper: VueWrapper
const mockTask = {
id: '1',
title: 'Test Task',
status: { name: 'Todo', color: '#6B7280' },
priority: { name: 'High', color: '#EF4444' }
}
beforeEach(() => {
wrapper = mount(TaskCard, {
props: { task: mockTask },
global: {
plugins: [createTestingPinia()]
}
})
})
it('renders task title', () => {
expect(wrapper.text()).toContain('Test Task')
})
it('emits update event on save', async () => {
await wrapper.find('[data-testid="save-btn"]').trigger('click')
expect(wrapper.emitted('update')).toBeTruthy()
})
})
Backend Route Test Pattern
import { describe, it, expect, vi, beforeEach } from 'vitest'
import request from 'supertest'
import express from 'express'
import tasksRouter from '../routes/tasks.js'
vi.mock('../lib/prisma.js', () => ({
prisma: {
task: {
findMany: vi.fn(),
findUnique: vi.fn(),
create: vi.fn(),
update: vi.fn(),
delete: vi.fn()
}
}
}))
import { prisma } from '../lib/prisma.js'
describe('Tasks Routes', () => {
let app: express.Application
beforeEach(() => {
app = express()
app.use(express.json())
app.use('/api/tasks', tasksRouter)
vi.clearAllMocks()
})
describe('GET /api/tasks', () => {
it('returns all tasks', () => {
mockTasks = [
{ : , : , : { : } }
]
vi.(prisma..).(mockTasks)
response = (app).()
(response.).()
(response.).(mockTasks)
})
})
(, {
(, () => {
newTask = { : , : }
created = { : , ...newTask }
vi.(prisma..).(created)
response = (app)
.()
.(newTask)
(response.).()
(response.).(created)
})
})
(, {
(, () => {
vi.(prisma..).()
response = (app).()
(response.).()
})
})
})
Critical Rules
- Use
data-testid selectors – not CSS classes
- Mock at module level – before imports
- Clear mocks in
beforeEach – prevent test pollution
- Use AAA pattern – Arrange, Act, Assert
- Test behavior, not implementation
Pinia Store Mocking
import { createTestingPinia } from '@pinia/testing'
const wrapper = mount(Component, {
global: {
plugins: [
createTestingPinia({
initialState: {
tasks: {
tasks: [mockTask],
isLoading: false
}
}
})
]
}
})
const store = useTasksStore()
expect(store.tasks).toHaveLength(1)
Vue Router Mocking
import { createRouter, createMemoryHistory } from 'vue-router'
const router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/', component: { template: '<div />' } }
]
})
const wrapper = mount(Component, {
global: {
plugins: [router]
}
})
await router.isReady()
Testing Async Operations
it('handles async action', async () => {
const mockData = { id: '1', title: 'Task' }
vi.mocked(fetchData).mockResolvedValue(mockData)
const { result } = await doAsyncOperation()
expect(result).toEqual(mockData)
})
it('handles async errors', async () => {
vi.mocked(fetchData).mockRejectedValue(new Error('Failed'))
await expect(doAsyncOperation()).rejects.toThrow('Failed')
})
Test Coverage Checklist
For each unit:
Commands
npm run test
cd backend && npm run test:run
npm run test -- --watch
npm run test -- --coverage