# Vitest Mocking Guide for SvelteKit

This guide explains how to properly mock dependencies when testing SvelteKit applications with Vitest.

## Understanding Mocking in SvelteKit Context

SvelteKit has a unique architecture where code can run on both server and client. This affects how we mock:

1. **Server-only modules** (`$lib/server/*`, `*.server.ts`) - Only run on server
2. **Universal modules** - Can run on both server and client
3. **Environment variables** - Different modules for static vs dynamic access

## Key Principles

1. **`vi.mock()` is hoisted** - Always executed before imports
2. **Use factory functions** - Return mocked implementations
3. **Mock before import** - Mocks must be defined before the module is imported
4. **Clean up** - Always restore/reset mocks in `beforeEach` or `afterEach`

---

## Mocking Environment Variables ($env/dynamic/private)

**Problem:** Can't directly mock `$env/dynamic/private` because it's a SvelteKit magic module.

**Solution:** Create a config module that wraps env access, then mock the config.

### Example: Queue Config Module

```typescript
// src/lib/server/queue/config.ts
import { env } from '$env/dynamic/private';

export const queueConfig = {
	concurrency: parseInt(env.QUEUE_CONCURRENCY || '2', 10),
	maxRetries: parseInt(env.QUEUE_MAX_RETRIES || '3', 10),
	tandoor: {
		enabled: !!env.TANDOOR_TOKEN,
		token: env.TANDOOR_TOKEN || null
	}
};
```

### Mocking the Config in Tests

```typescript
import { vi, beforeEach, afterEach } from 'vitest';
import * as queueConfigModule from '$lib/server/queue/config';

// Mock the config module
vi.mock('$lib/server/queue/config', () => ({
	queueConfig: {
		concurrency: 2,
		maxRetries: 3,
		tandoor: { enabled: true, token: 'test-token' }
	}
}));

describe('QueueProcessor', () => {
	beforeEach(() => {
		vi.clearAllMocks();
	});

	afterEach(() => {
		vi.restoreAllMocks();
	});
});
```

---

## Mocking External Service Modules

### Recommended Approach: Mock Entire Module

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

// IMPORTANT: Mock BEFORE importing the module that uses it
vi.mock('$lib/server/extraction', () => ({
	extractTextAndThumbnail: vi.fn().mockResolvedValue({
		bodyText: 'Mock recipe text',
		thumbnail: 'https://mock.com/image.jpg'
	})
}));

// NOW import the module that depends on these
import { queueProcessor } from '$lib/server/queue/QueueProcessor';
import { extractTextAndThumbnail } from '$lib/server/extraction';

describe('QueueProcessor', () => {
	it('should use mocked services', async () => {
		const item = queueManager.enqueue('https://instagram.com/p/test');

		// Verify mock was called
		expect(extractTextAndThumbnail).toHaveBeenCalledWith(
			'https://instagram.com/p/test',
			expect.any(Function)
		);
	});
});
```

---

## Mocking API Endpoints (SvelteKit RequestHandler)

When testing API endpoints, be aware that error responses must be properly awaited:

```typescript
import { describe, it, expect } from 'vitest';
import { POST } from '../routes/api/queue/+server';

describe('POST /api/queue', () => {
	it('should reject invalid URLs', async () => {
		const request = new Request('http://localhost/api/queue', {
			method: 'POST',
			headers: { 'Content-Type': 'application/json' },
			body: JSON.stringify({ url: 'invalid-url' })
		});

		const response = await POST({ request } as any);

		// ✅ CORRECT - Check status first
		expect(response.status).toBe(400);

		// ✅ CORRECT - Properly await error response
		const data = await response.json();
		expect(data.message).toContain('Invalid');
	});
});
```

### Common Pitfall: Not Awaiting Error Responses

```typescript
// ❌ WRONG - This will fail
it('should reject invalid input', async () => {
	const response = await endpoint({ request } as any);
	const data = response.json(); // Missing await!
	expect(data.message).toBe('Error'); // data is a Promise
});

// ✅ CORRECT
it('should reject invalid input', async () => {
	const response = await endpoint({ request } as any);
	expect(response.status).toBe(400);
	const data = await response.json(); // Properly awaited
	expect(data.message).toBe('Error');
});
```

---

## Common Pitfalls and Solutions

### Problem 1: Mock Not Working

```typescript
// ❌ WRONG - Import before mock
import { queueProcessor } from './QueueProcessor';
vi.mock('./extraction');

// ✅ CORRECT - Mock before import
vi.mock('./extraction');
import { queueProcessor } from './QueueProcessor';
```

### Problem 2: Mocks Not Resetting Between Tests

```typescript
// ✅ SOLUTION - Always clean up
import { beforeEach, afterEach } from 'vitest';

beforeEach(() => {
	vi.clearAllMocks(); // Clear call history
});

afterEach(() => {
	vi.restoreAllMocks(); // Restore original implementations
});
```

### Problem 3: TypeScript Errors with Mocked Functions

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

// ✅ CORRECT - Type assertion
const mockFn = vi.fn<() => Promise<string>>();
mockFn.mockResolvedValue('test');

// OR use vi.mocked()
import { vi, type Mock } from 'vitest';
const mockFn = vi.fn() as Mock<() => Promise<string>>;
```

---

## Testing Async Queue Processing

### Solution 1: Wait for Processing

```typescript
it('should process item', async () => {
	const item = queueManager.enqueue('https://instagram.com/p/test');

	// Wait for processing with timeout
	await vi.waitFor(
		() => {
			const updated = queueManager.get(item.id);
			expect(updated?.status).toBe('success');
		},
		{ timeout: 5000, interval: 100 }
	);
});
```

### Solution 2: Use Fake Timers

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

beforeEach(() => {
	vi.useFakeTimers();
});

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

it('should process after delay', async () => {
	queueManager.enqueue('https://test.com');

	// Fast-forward time
	await vi.advanceTimersByTimeAsync(1000);

	// Now check results
});
```

---

## Best Practices for SvelteKit + Vitest

1. **Always mock before import** - `vi.mock()` calls are hoisted but still need to be before your imports
2. **Use factory functions** - Return new instances to avoid state leaking between tests
3. **Clean up thoroughly** - Use `beforeEach`/`afterEach` to reset state
4. **Type your mocks** - Use TypeScript generics for type-safe mocks
5. **Test isolation** - Each test should be independent
6. **Mock at the right level** - Mock external boundaries (HTTP, DB), not internal logic
7. **Use `vi.waitFor()`** - For async operations instead of arbitrary `setTimeout()`
8. **Snapshot complex mocks** - Use `expect.any(Function)` for callbacks
9. **Always await `.json()` on responses** - Both success and error responses

---

## Quick Reference: Mock Cheat Sheet

```typescript
// Mock entire module
vi.mock('./module', () => ({ export: vi.fn() }));

// Mock with factory
vi.mock('./module', () => {
	return { dynamicExport: () => 'value' };
});

// Spy on existing export
vi.spyOn(module, 'export').mockReturnValue('value');

// Mock return value
mockFn.mockReturnValue('sync value');
mockFn.mockResolvedValue('async value');
mockFn.mockRejectedValue(new Error('async error'));

// Mock implementation
mockFn.mockImplementation((arg) => arg * 2);
mockFn.mockImplementationOnce((arg) => arg * 3);

// Check calls
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledTimes(2);
expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2');
expect(mockFn).toHaveBeenLastCalledWith('arg');

// Reset/restore
vi.clearAllMocks(); // Clear call history
vi.resetAllMocks(); // + Reset implementations
vi.restoreAllMocks(); // + Restore original implementations

// Environment variables
vi.stubEnv('VAR_NAME', 'value');
vi.unstubAllEnvs();

// Timers
vi.useFakeTimers();
vi.advanceTimersByTime(1000);
await vi.advanceTimersByTimeAsync(1000);
vi.useRealTimers();

// Async helpers
await vi.waitFor(() => expect(condition).toBe(true));
await vi.waitUntil(() => condition === true);
```

---

## Examples from This Project

See the following test files for real-world examples:

- `src/tests/queue-manager.spec.ts` - Mocking external services
- `src/tests/queue-processor.spec.ts` - Mocking config module and services
- `src/tests/queue-api.spec.ts` - Testing API endpoints with proper async handling

---

## Additional Resources

- [Vitest Mocking Guide](https://vitest.dev/guide/mocking.html)
- [SvelteKit Testing](https://kit.svelte.dev/docs/testing)
- [Vitest API Reference](https://vitest.dev/api/)
