simplify
This commit is contained in:
158
docs/TESTING.md
158
docs/TESTING.md
@@ -7,7 +7,7 @@ This guide explains how to properly mock dependencies when testing SvelteKit app
|
||||
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
|
||||
2. **Universal modules** - Can run on both server and client
|
||||
3. **Environment variables** - Different modules for static vs dynamic access
|
||||
|
||||
## Key Principles
|
||||
@@ -32,12 +32,12 @@ SvelteKit has a unique architecture where code can run on both server and client
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
@@ -49,21 +49,21 @@ 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' }
|
||||
}
|
||||
queueConfig: {
|
||||
concurrency: 2,
|
||||
maxRetries: 3,
|
||||
tandoor: { enabled: true, token: 'test-token' }
|
||||
}
|
||||
}));
|
||||
|
||||
describe('QueueProcessor', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
@@ -78,10 +78,10 @@ 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'
|
||||
})
|
||||
extractTextAndThumbnail: vi.fn().mockResolvedValue({
|
||||
bodyText: 'Mock recipe text',
|
||||
thumbnail: 'https://mock.com/image.jpg'
|
||||
})
|
||||
}));
|
||||
|
||||
// NOW import the module that depends on these
|
||||
@@ -89,15 +89,15 @@ 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)
|
||||
);
|
||||
});
|
||||
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)
|
||||
);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
@@ -112,22 +112,22 @@ 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');
|
||||
});
|
||||
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');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
@@ -136,17 +136,17 @@ describe('POST /api/queue', () => {
|
||||
```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
|
||||
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');
|
||||
const response = await endpoint({ request } as any);
|
||||
expect(response.status).toBe(400);
|
||||
const data = await response.json(); // Properly awaited
|
||||
expect(data.message).toBe('Error');
|
||||
});
|
||||
```
|
||||
|
||||
@@ -173,11 +173,11 @@ import { queueProcessor } from './QueueProcessor';
|
||||
import { beforeEach, afterEach } from 'vitest';
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks(); // Clear call history
|
||||
vi.clearAllMocks(); // Clear call history
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks(); // Restore original implementations
|
||||
vi.restoreAllMocks(); // Restore original implementations
|
||||
});
|
||||
```
|
||||
|
||||
@@ -203,16 +203,16 @@ const mockFn = vi.fn() as Mock<() => Promise<string>>;
|
||||
|
||||
```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 }
|
||||
);
|
||||
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 }
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
@@ -222,20 +222,20 @@ it('should process item', async () => {
|
||||
import { vi } from 'vitest';
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('should process after delay', async () => {
|
||||
queueManager.enqueue('https://test.com');
|
||||
|
||||
// Fast-forward time
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
|
||||
// Now check results
|
||||
queueManager.enqueue('https://test.com');
|
||||
|
||||
// Fast-forward time
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
|
||||
// Now check results
|
||||
});
|
||||
```
|
||||
|
||||
@@ -263,7 +263,7 @@ vi.mock('./module', () => ({ export: vi.fn() }));
|
||||
|
||||
// Mock with factory
|
||||
vi.mock('./module', () => {
|
||||
return { dynamicExport: () => 'value' };
|
||||
return { dynamicExport: () => 'value' };
|
||||
});
|
||||
|
||||
// Spy on existing export
|
||||
@@ -285,9 +285,9 @@ 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
|
||||
vi.clearAllMocks(); // Clear call history
|
||||
vi.resetAllMocks(); // + Reset implementations
|
||||
vi.restoreAllMocks(); // + Restore original implementations
|
||||
|
||||
// Environment variables
|
||||
vi.stubEnv('VAR_NAME', 'value');
|
||||
|
||||
Reference in New Issue
Block a user