Modern testing with Vitest including mocking, snapshots, coverage, and component testing
# Vitest Testing Patterns for Google Antigravity
Master modern testing with Vitest using Google Antigravity's Gemini 3 engine. This guide covers unit tests, mocking strategies, component testing, and coverage configuration.
## Vitest Configuration
```typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./tests/setup.ts'],
include: ['**/*.{test,spec}.{ts,tsx}'],
exclude: ['**/node_modules/**', '**/e2e/**'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: [
'node_modules/',
'tests/setup.ts',
'**/*.d.ts',
'**/*.config.*',
],
thresholds: {
lines: 80,
branches: 80,
functions: 80,
statements: 80,
},
},
mockReset: true,
restoreMocks: true,
},
resolve: {
alias: {
'@': resolve(__dirname, './src'),
},
},
});
// tests/setup.ts
import '@testing-library/jest-dom/vitest';
import { afterEach, vi } from 'vitest';
import { cleanup } from '@testing-library/react';
// Cleanup after each test
afterEach(() => {
cleanup();
});
// Mock window.matchMedia
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
});
// Mock IntersectionObserver
global.IntersectionObserver = vi.fn().mockImplementation(() => ({
observe: vi.fn(),
unobserve: vi.fn(),
disconnect: vi.fn(),
}));
```
## Unit Testing Functions
```typescript
// lib/utils.ts
export function formatPrice(cents: number, currency = 'USD'): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency,
}).format(cents / 100);
}
export function slugify(text: string): string {
return text
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_-]+/g, '-')
.replace(/^-+|-+$/g, '');
}
export function debounce<T extends (...args: any[]) => any>(
fn: T,
delay: number
): (...args: Parameters<T>) => void {
let timeoutId: ReturnType<typeof setTimeout>;
return (...args: Parameters<T>) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn(...args), delay);
};
}
// lib/utils.test.ts
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { formatPrice, slugify, debounce } from './utils';
describe('formatPrice', () => {
it('formats cents to USD by default', () => {
expect(formatPrice(1000)).toBe('$10.00');
expect(formatPrice(999)).toBe('$9.99');
expect(formatPrice(0)).toBe('$0.00');
});
it('formats with different currencies', () => {
expect(formatPrice(1000, 'EUR')).toMatch(/€|EUR/);
expect(formatPrice(1000, 'GBP')).toMatch(/£|GBP/);
});
it('handles large numbers', () => {
expect(formatPrice(100000000)).toBe('$1,000,000.00');
});
});
describe('slugify', () => {
it('converts text to slug format', () => {
expect(slugify('Hello World')).toBe('hello-world');
expect(slugify(' Multiple Spaces ')).toBe('multiple-spaces');
expect(slugify('Special!@#Characters')).toBe('specialcharacters');
});
it('handles edge cases', () => {
expect(slugify('')).toBe('');
expect(slugify('---')).toBe('');
expect(slugify('Already-Slugified')).toBe('already-slugified');
});
});
describe('debounce', () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it('delays function execution', () => {
const fn = vi.fn();
const debounced = debounce(fn, 100);
debounced();
expect(fn).not.toHaveBeenCalled();
vi.advanceTimersByTime(100);
expect(fn).toHaveBeenCalledTimes(1);
});
it('cancels previous calls', () => {
const fn = vi.fn();
const debounced = debounce(fn, 100);
debounced();
debounced();
debounced();
vi.advanceTimersByTime(100);
expect(fn).toHaveBeenCalledTimes(1);
});
it('passes arguments correctly', () => {
const fn = vi.fn();
const debounced = debounce(fn, 100);
debounced('arg1', 'arg2');
vi.advanceTimersByTime(100);
expect(fn).toHaveBeenCalledWith('arg1', 'arg2');
});
});
```
## Mocking Modules
```typescript
// services/api.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { fetchProducts, createProduct } from './api';
// Mock global fetch
const mockFetch = vi.fn();
global.fetch = mockFetch;
describe('API Service', () => {
beforeEach(() => {
mockFetch.mockReset();
});
describe('fetchProducts', () => {
it('fetches products successfully', async () => {
const products = [{ id: '1', name: 'Product 1' }];
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(products),
});
const result = await fetchProducts();
expect(mockFetch).toHaveBeenCalledWith(
expect.stringContaining('/api/products'),
expect.any(Object)
);
expect(result).toEqual(products);
});
it('throws on API error', async () => {
mockFetch.mockResolvedValueOnce({
ok: false,
status: 500,
statusText: 'Internal Server Error',
});
await expect(fetchProducts()).rejects.toThrow('API Error');
});
});
});
// Mocking modules
vi.mock('@/lib/supabase', () => ({
createClient: vi.fn(() => ({
from: vi.fn(() => ({
select: vi.fn().mockReturnThis(),
insert: vi.fn().mockReturnThis(),
update: vi.fn().mockReturnThis(),
delete: vi.fn().mockReturnThis(),
eq: vi.fn().mockReturnThis(),
single: vi.fn().mockResolvedValue({ data: {}, error: null }),
})),
})),
}));
```
## Component Testing
```typescript
// components/ProductCard.test.tsx
import { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ProductCard } from './ProductCard';
const mockProduct = {
id: '1',
name: 'Test Product',
price: 2999,
description: 'A great product',
imageUrl: '/test.jpg',
};
describe('ProductCard', () => {
it('renders product information', () => {
render(<ProductCard product={mockProduct} />);
expect(screen.getByText('Test Product')).toBeInTheDocument();
expect(screen.getByText('$29.99')).toBeInTheDocument();
expect(screen.getByText('A great product')).toBeInTheDocument();
});
it('calls onAddToCart when button clicked', async () => {
const onAddToCart = vi.fn();
const user = userEvent.setup();
render(<ProductCard product={mockProduct} onAddToCart={onAddToCart} />);
await user.click(screen.getByRole('button', { name: /add to cart/i }));
expect(onAddToCart).toHaveBeenCalledWith(mockProduct.id);
});
it('shows loading state', () => {
render(<ProductCard product={mockProduct} isLoading />);
expect(screen.getByTestId('loading-skeleton')).toBeInTheDocument();
});
it('handles missing image gracefully', () => {
const productWithoutImage = { ...mockProduct, imageUrl: undefined };
render(<ProductCard product={productWithoutImage} />);
expect(screen.getByTestId('placeholder-image')).toBeInTheDocument();
});
});
```
## Testing Hooks
```typescript
// hooks/useCounter.test.ts
import { describe, it, expect } from 'vitest';
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';
describe('useCounter', () => {
it('initializes with default value', () => {
const { result } = renderHook(() => useCounter());
expect(result.current.count).toBe(0);
});
it('initializes with custom value', () => {
const { result } = renderHook(() => useCounter(10));
expect(result.current.count).toBe(10);
});
it('increments count', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
it('decrements count', () => {
const { result } = renderHook(() => useCounter(5));
act(() => {
result.current.decrement();
});
expect(result.current.count).toBe(4);
});
it('resets to initial value', () => {
const { result } = renderHook(() => useCounter(10));
act(() => {
result.current.increment();
result.current.increment();
result.current.reset();
});
expect(result.current.count).toBe(10);
});
});
```
## Best Practices
Google Antigravity's Gemini 3 engine recommends these Vitest patterns: Use describe blocks to organize related tests. Mock external dependencies consistently. Test edge cases and error conditions. Use userEvent for realistic user interactions. Maintain high coverage thresholds for critical code.This Vitest prompt is ideal for developers working on:
By using this prompt, you can save hours of manual coding and ensure best practices are followed from the start. It's particularly valuable for teams looking to maintain consistency across their vitest implementations.
Yes! All prompts on Antigravity AI Directory are free to use for both personal and commercial projects. No attribution required, though it's always appreciated.
This prompt works excellently with Claude, ChatGPT, Cursor, GitHub Copilot, and other modern AI coding assistants. For best results, use models with large context windows.
You can modify the prompt by adding specific requirements, constraints, or preferences. For Vitest projects, consider mentioning your framework version, coding style, and any specific libraries you're using.