Skip to content

Testing Patterns

This document outlines patterns for testing components of the ReX content system. Effective testing ensures system reliability, helps prevent regressions, and validates that components work as expected across different environments.

Unit Testing Pattern

Pattern Overview

The Unit Testing pattern validates individual components in isolation from their dependencies.

Implementation Example

typescript
import { describe, test, expect, vi } from 'vitest'
import { createMemoryAdapter } from '@lib/content/adapters/common/memory'
import { ContentNotFoundError } from '@lib/errors/content'

// Unit tests for memory adapter
describe('MemoryAdapter', () => {
  // Reset adapter between tests
  let adapter

  beforeEach(() => {
    adapter = createMemoryAdapter()
  })

  // Test read operation
  test('read returns content when it exists', async () => {
    // Setup: Add content to adapter
    const content = {
      data: 'Test content',
      contentType: 'text/plain',
      metadata: { title: 'Test' },
    }

    await adapter.write('test.md', content)

    // Test: Read content
    const result = await adapter.read('test.md')

    // Assert: Content matches what was written
    expect(result).toEqual(content)
  })

  // Test error case
  test('read throws ContentNotFoundError for missing content', async () => {
    // Test: Try to read non-existent content
    await expect(adapter.write('test')).rejects.toThrow(ContentNotFoundError)
  })

  // Test write and delete operations
  test('write and delete operations update storage correctly', async () => {
    // Setup: Initial content
    const content = {
      data: 'Test content',
      contentType: 'text/plain',
      metadata: { title: 'Test' },
    }

    // Test: Write operation
    await adapter.write('test.md', content)

    // Assert: Content exists after write
    const afterWrite = await adapter.read('test.md')
    expect(afterWrite).toEqual(content)

    // Test: Delete operation
    await adapter.delete('test.md')

    // Assert: Content doesn't exist after delete
    await expect(adapter.read('test.md')).rejects.toThrow(ContentNotFoundError)
  })

  // Test events
  test('events are emitted for write and delete operations', async () => {
    // Setup: Event listeners
    const writeListener = vi.fn()
    const deleteListener = vi.fn()

    adapter.events.on('change', event => {
      if (event.type === 'write') {
        writeListener(event)
      } else if (event.type === 'delete') {
        deleteListener(event)
      }
    })

    // Test: Write operation
    const content = {
      data: 'Test content',
      contentType: 'text/plain',
      metadata: { title: 'Test' },
    }

    await adapter.write('test.md', content)

    // Assert: Write event was emitted
    expect(writeListener).toHaveBeenCalledWith(
      expect.objectContaining({
        uri: 'test.md',
        content,
        type: 'write',
      })
    )

    // Test: Delete operation
    await adapter.delete('test.md')

    // Assert: Delete event was emitted
    expect(deleteListener).toHaveBeenCalledWith(
      expect.objectContaining({
        uri: 'test.md',
        content: null,
        type: 'delete',
      })
    )
  })

  // Test list operation
  test('list returns content URIs matching pattern', async () => {
    // Setup: Add content
    await adapter.write('docs/a.md', {
      data: 'A',
      contentType: 'text/markdown',
    })
    await adapter.write('docs/b.md', {
      data: 'B',
      contentType: 'text/markdown',
    })
    await adapter.write('docs/sub/c.md', {
      data: 'C',
      contentType: 'text/markdown',
    })
    await adapter.write('other/d.md', {
      data: 'D',
      contentType: 'text/markdown',
    })

    // Test: List with patterns
    const allDocs = await adapter.list('docs/**')
    const rootDocs = await adapter.list('docs/*.md')
    const all = await adapter.list('**')

    // Assert: Correct URIs returned
    expect(allDocs).toHaveLength(3)
    expect(allDocs).toContain('docs/a.md')
    expect(allDocs).toContain('docs/b.md')
    expect(allDocs).toContain('docs/sub/c.md')

    expect(rootDocs).toHaveLength(2)
    expect(rootDocs).toContain('docs/a.md')
    expect(rootDocs).toContain('docs/b.md')

    expect(all).toHaveLength(4)
  })
})

Considerations

  • Test each component in isolation with dependencies mocked
  • Focus on the component’s public API rather than implementation details
  • Include both success and error paths
  • Use setup and teardown to ensure tests are independent

Integration Testing Pattern

Pattern Overview

The Integration Testing pattern tests how components work together in realistic configurations.

Implementation Example

typescript
import { describe, test, expect, vi } from 'vitest'
import { createContentStore } from '@lib/content/store/factory'
import { createMemoryAdapter } from '@lib/content/adapters/common/memory'
import { withValidation } from '@lib/content/middleware/validation'
import { withLogging } from '@lib/content/middleware/logging'
import { ContentValidationError } from '@lib/errors/content'

// Integration tests for content store with middleware
describe('ContentStore with middleware', () => {
  // Create test schema
  const testSchema = {
    validate: content => {
      // Simple validation: must have data and contentType
      if (!content.data) {
        return { valid: false, errors: ['Missing content data'] }
      }

      if (!content.contentType) {
        return { valid: false, errors: ['Missing content type'] }
      }

      return { valid: true }
    },
  }

  // Mock logger
  const mockLogger = {
    log: vi.fn(),
    error: vi.fn(),
    warn: vi.fn(),
    info: vi.fn(),
    debug: vi.fn(),
  }

  // Setup test store
  let store

  beforeEach(() => {
    // Reset mocks
    vi.resetAllMocks()

    // Create adapter
    const adapter = createMemoryAdapter()

    // Create enhanced store with middleware
    store = createContentStore({
      adapter,
      enhancers: [
        withValidation(testSchema),
        withLogging({ logger: mockLogger }),
      ],
    })
  })

  // Test valid content write with middleware chain
  test('valid content passes through middleware chain', async () => {
    // Setup: Valid content
    const content = {
      data: 'Test content',
      contentType: 'text/plain',
      metadata: { title: 'Test' },
    }

    // Test: Write content
    await store.write('test.md', content)

    // Assert: Content was written
    const result = await store.read('test.md')
    expect(result).toEqual(content)

    // Assert: Logger was called
    expect(mockLogger.info).toHaveBeenCalledWith(
      expect.stringContaining('write'),
      expect.objectContaining({
        uri: 'test.md',
        operation: 'write',
      })
    )
  })

  // Test validation error
  test('invalid content is rejected by validation middleware', async () => {
    // Setup: Invalid content (missing contentType)
    const invalidContent = {
      data: 'Test content',
      metadata: { title: 'Test' },
    }

    // Test: Try to write invalid content
    await expect(store.write('test.md', invalidContent)).rejects.toThrow(
      ContentValidationError
    )

    // Assert: Error was logged
    expect(mockLogger.error).toHaveBeenCalledWith(
      expect.stringContaining('validation'),
      expect.objectContaining({
        uri: 'test.md',
        operation: 'write',
        error: expect.any(ContentValidationError),
      })
    )
  })

  // Test middleware order
  test('middleware executes in correct order', async () => {
    // Setup: Capture order of execution
    const executionOrder = []

    // Create tracking middleware
    const trackingMiddleware1 = next => async params => {
      executionOrder.push('middleware1:before')
      const result = await next(params)
      executionOrder.push('middleware1:after')
      return result
    }

    const trackingMiddleware2 = next => async params => {
      executionOrder.push('middleware2:before')
      const result = await next(params)
      executionOrder.push('middleware2:after')
      return result
    }

    // Create store with tracking middleware
    const orderedStore = createContentStore({
      adapter: createMemoryAdapter(),
      enhancers: [trackingMiddleware1, trackingMiddleware2],
    })

    // Test: Execute operation
    await orderedStore.read('test.md').catch(() => {})

    // Assert: Middleware executed in correct order
    expect(executionOrder).toEqual([
      'middleware1:before',
      'middleware2:before',
      'middleware2:after',
      'middleware1:after',
    ])
  })
})

Considerations

  • Test realistic combinations of components
  • Focus on interaction points between components
  • Use controlled test data that exercises integration points
  • Verify both functional correctness and error handling

Mock Implementation Pattern

Pattern Overview

The Mock Implementation pattern creates controllable stand-ins for real dependencies.

Implementation Example

typescript
import { describe, test, expect, vi } from 'vitest'
import { createAdapter } from '@lib/content/adapters/base'
import { createEventEmitter } from '@lib/utils/events'

// Create mock adapter factory
export function createMockAdapter(options = {}) {
  // Set up storage
  const storage = new Map()

  // Create controlled event emitter
  const events = createEventEmitter()

  // Default behavior
  const behavior = {
    // Control read behavior
    readSuccess: true,
    readDelay: 0,

    // Control write behavior
    writeSuccess: true,
    writeDelay: 0,

    // Control delete behavior
    deleteSuccess: true,
    deleteDelay: 0,

    // Control list behavior
    listSuccess: true,
    listDelay: 0,

    ...options.behavior,
  }

  // Mock implementations with controlled behavior
  const read = vi.fn(async uri => {
    // Apply configured delay
    if (behavior.readDelay > 0) {
      await new Promise(resolve => setTimeout(resolve, behavior.readDelay))
    }

    // Fail if configured to do so
    if (!behavior.readSuccess) {
      throw new Error('Controlled read failure')
    }

    // Check if content exists
    if (!storage.has(uri)) {
      throw new ContentNotFoundError(uri)
    }

    // Return the content
    return storage.get(uri)
  })

  const write = vi.fn(async (uri, content) => {
    // Apply configured delay
    if (behavior.writeDelay > 0) {
      await new Promise(resolve => setTimeout(resolve, behavior.writeDelay))
    }

    // Fail if configured to do so
    if (!behavior.writeSuccess) {
      throw new Error('Controlled write failure')
    }

    // Store the content
    storage.set(uri, { ...content })

    // Emit change event
    events.emit('change', {
      uri,
      content,
      type: 'write',
      timestamp: Date.now(),
    })
  })

  const deleteOp = vi.fn(async uri => {
    // Apply configured delay
    if (behavior.deleteDelay > 0) {
      await new Promise(resolve => setTimeout(resolve, behavior.deleteDelay))
    }

    // Fail if configured to do so
    if (!behavior.deleteSuccess) {
      throw new Error('Controlled delete failure')
    }

    // Check if content exists
    if (!storage.has(uri)) {
      throw new ContentNotFoundError(uri)
    }

    // Delete the content
    storage.delete(uri)

    // Emit change event
    events.emit('change', {
      uri,
      content: null,
      type: 'delete',
      timestamp: Date.now(),
    })
  })

  const list = vi.fn(async pattern => {
    // Apply configured delay
    if (behavior.listDelay > 0) {
      await new Promise(resolve => setTimeout(resolve, behavior.listDelay))
    }

    // Fail if configured to do so
    if (!behavior.listSuccess) {
      throw new Error('Controlled list failure')
    }

    // Convert pattern to regex
    const regex = new RegExp(`^${pattern.replace(/\*/g, '.*')}$`)

    // Filter URIs by pattern
    const uris = Array.from(storage.keys()).filter(uri => regex.test(uri))

    return uris
  })

  // Helper to directly manipulate stored content (for test setup)
  const setStoredContent = (uri, content) => {
    storage.set(uri, { ...content })
  }

  // Helper to directly check stored content (for assertions)
  const getStoredContent = uri => {
    return storage.get(uri)
  }

  // Create the adapter
  const adapter = createAdapter({
    read,
    write,
    delete: deleteOp,
    list,
    events,

    // Additional testing helpers
    __test__: {
      setStoredContent,
      getStoredContent,
      manipulateStorage: storage,
      setBehavior: newBehavior => {
        Object.assign(behavior, newBehavior)
      },
      resetBehavior: () => {
        Object.assign(behavior, options.behavior || {})
      },
    },
  })

  return adapter
}

// Example mock adapter test
describe('MockAdapter', () => {
  // Test mock adapter with controlled behavior
  test('mock adapter returns controlled results', async () => {
    // Create mock with default success behavior
    const adapter = createMockAdapter()

    // Setup test content
    adapter.__test__.setStoredContent('test.md', {
      data: 'Test content',
      contentType: 'text/plain',
      metadata: { title: 'Test' },
    })

    // Test: Read succeeds
    const content = await adapter.read('test.md')
    expect(content.data).toBe('Test content')

    // Change behavior to simulate failure
    adapter.__test__.setBehavior({ readSuccess: false })

    // Test: Read now fails
    await expect(adapter.read('test.md')).rejects.toThrow(
      'Controlled read failure'
    )

    // Verify write was called with expected arguments
    await adapter.write('new.md', {
      data: 'New content',
      contentType: 'text/plain',
    })
    expect(adapter.write).toHaveBeenCalledWith(
      'new.md',
      expect.objectContaining({ data: 'New content' })
    )
  })

  // Test delays
  test('mock adapter honors configured delays', async () => {
    // Create mock with significant delay
    const adapter = createMockAdapter({
      behavior: { readDelay: 100 },
    })

    // Setup test content
    adapter.__test__.setStoredContent('test.md', {
      data: 'Test content',
      contentType: 'text/plain',
    })

    // Test: Measure operation time
    const start = Date.now()
    await adapter.read('test.md')
    const duration = Date.now() - start

    // Assert: Operation took at least the configured delay
    expect(duration).toBeGreaterThanOrEqual(100)
  })
})

Considerations

  • Make mocks controllable with explicit behavior settings
  • Include both success and failure modes in mock implementations
  • Add testing-specific helpers that aren’t part of the real API
  • Document mock behavior clearly for test authors

Environment Simulation Pattern

Pattern Overview

The Environment Simulation pattern creates virtual environments for testing environment-specific code.

Implementation Example

typescript
import { describe, test, expect, vi } from 'vitest'
import { createOptimalAdapter } from '@lib/content/adapters/factory'
import { isNode, isBrowser, isServiceWorker } from '@lib/utils/env'

// Mock environment detection
vi.mock('@lib/utils/env', () => ({
  isNode: vi.fn(() => false),
  isBrowser: vi.fn(() => false),
  isServiceWorker: vi.fn(() => false),
  hasIndexedDB: vi.fn(() => false),
  hasLocalStorage: vi.fn(() => false),
}))

// Mock implementations
const mockFileSystemAdapter = vi.fn(() => ({ type: 'filesystem' }))
const mockIndexedDBAdapter = vi.fn(() => ({ type: 'indexeddb' }))
const mockLocalStorageAdapter = vi.fn(() => ({ type: 'localstorage' }))
const mockServiceWorkerAdapter = vi.fn(() => ({ type: 'serviceworker' }))
const mockMemoryAdapter = vi.fn(() => ({ type: 'memory' }))

vi.mock('@lib/content/adapters/node/filesystem', () => ({
  createFilesystemAdapter: mockFileSystemAdapter,
}))

vi.mock('@lib/content/adapters/browser/indexed-db', () => ({
  createIndexedDBAdapter: mockIndexedDBAdapter,
}))

vi.mock('@lib/content/adapters/browser/local-storage', () => ({
  createLocalStorageAdapter: mockLocalStorageAdapter,
}))

vi.mock('@lib/content/adapters/browser/service-worker', () => ({
  createServiceWorkerAdapter: mockServiceWorkerAdapter,
}))

vi.mock('@lib/content/adapters/common/memory', () => ({
  createMemoryAdapter: mockMemoryAdapter,
}))

// Environment simulation tests
describe('OptimalAdapter environment detection', () => {
  // Reset mocks between tests
  beforeEach(() => {
    vi.resetAllMocks()

    // Default all environments to false
    isNode.mockReturnValue(false)
    isBrowser.mockReturnValue(false)
    isServiceWorker.mockReturnValue(false)
  })

  // Test Node.js environment
  test('selects filesystem adapter in Node.js environment', () => {
    // Simulate Node.js environment
    isNode.mockReturnValue(true)

    // Create adapter
    const adapter = createOptimalAdapter()

    // Verify correct adapter was created
    expect(mockFileSystemAdapter).toHaveBeenCalled()
    expect(adapter.type).toBe('filesystem')
  })

  // Test browser environment with IndexedDB
  test('selects IndexedDB adapter in browser with IndexedDB support', () => {
    // Simulate browser environment with IndexedDB
    isBrowser.mockReturnValue(true)

    // Mock feature detection
    const { hasIndexedDB } = require('@lib/utils/env')
    hasIndexedDB.mockReturnValue(true)

    // Create adapter
    const adapter = createOptimalAdapter()

    // Verify correct adapter was created
    expect(mockIndexedDBAdapter).toHaveBeenCalled()
    expect(adapter.type).toBe('indexeddb')
  })

  // Test browser environment with localStorage fallback
  test('selects localStorage adapter in browser without IndexedDB', () => {
    // Simulate browser environment without IndexedDB
    isBrowser.mockReturnValue(true)

    // Mock feature detection
    const { hasIndexedDB, hasLocalStorage } = require('@lib/utils/env')
    hasIndexedDB.mockReturnValue(false)
    hasLocalStorage.mockReturnValue(true)

    // Create adapter
    const adapter = createOptimalAdapter()

    // Verify correct adapter was created
    expect(mockLocalStorageAdapter).toHaveBeenCalled()
    expect(adapter.type).toBe('localstorage')
  })

  // Test service worker environment
  test('selects service worker adapter in service worker environment', () => {
    // Simulate service worker environment
    isServiceWorker.mockReturnValue(true)

    // Create adapter
    const adapter = createOptimalAdapter()

    // Verify correct adapter was created
    expect(mockServiceWorkerAdapter).toHaveBeenCalled()
    expect(adapter.type).toBe('serviceworker')
  })

  // Test fallback for unknown environment
  test('falls back to memory adapter for unknown environment', () => {
    // All environment detections are false

    // Create adapter
    const adapter = createOptimalAdapter()

    // Verify fallback adapter was created
    expect(mockMemoryAdapter).toHaveBeenCalled()
    expect(adapter.type).toBe('memory')
  })
})

Considerations

  • Mock environment detection utilities to simulate different environments
  • Test all supported environments and fallback paths
  • Maintain isolation between environment-specific tests
  • Reset mocked state between tests to prevent cross-test contamination

Test Data Factory Pattern

Pattern Overview

The Test Data Factory pattern provides consistent test data for different test scenarios.

Implementation Example

typescript
/**
 * Test data factories for content system tests
 */

// Factory for content items
export function createTestContent(overrides = {}) {
  return {
    data: 'Default test content',
    contentType: 'text/plain',
    metadata: {
      title: 'Test Content',
      created: '2025-01-01T00:00:00.000Z',
      modified: '2025-01-01T00:00:00.000Z',
      ...overrides.metadata,
    },
    ...overrides,
  }
}

// Factory for markdown content
export function createMarkdownContent(overrides = {}) {
  return createTestContent({
    data: '# Test Markdown\n\nThis is test markdown content.',
    contentType: 'text/markdown',
    metadata: {
      title: 'Test Markdown',
      ...overrides.metadata,
    },
    ...overrides,
  })
}

// Factory for JSON content
export function createJsonContent(overrides = {}) {
  return createTestContent({
    data: JSON.stringify({ test: true, value: 42 }),
    contentType: 'application/json',
    metadata: {
      title: 'Test JSON',
      ...overrides.metadata,
    },
    ...overrides,
  })
}

// Factory for binary content
export function createBinaryContent(overrides = {}) {
  // Create binary data (Uint8Array)
  const data = new Uint8Array([0x54, 0x65, 0x73, 0x74]) // "Test" in ASCII

  return createTestContent({
    data,
    contentType: 'application/octet-stream',
    metadata: {
      title: 'Test Binary',
      ...overrides.metadata,
    },
    ...overrides,
  })
}

// Factory for creating multiple content items
export function createTestContentSet(count = 5, factory = createTestContent) {
  const result = {}

  for (let i = 0; i < count; i++) {
    const uri = `test-${i}.${factory === createMarkdownContent ? 'md' : 'txt'}`
    result[uri] = factory({
      metadata: {
        title: `Test ${i}`,
        index: i,
      },
    })
  }

  return result
}

// Factory for content store operations
export function createTestOperations(uris = ['test.md']) {
  return {
    // Basic read operations
    read: uris.map(uri => ({
      type: 'read',
      uri,
      options: {},
    })),

    // Basic write operations
    write: uris.map(uri => ({
      type: 'write',
      uri,
      content: createTestContent({
        metadata: { title: `Content for ${uri}` },
      }),
      options: {},
    })),

    // Write with different content types
    writeMixed: [
      {
        type: 'write',
        uri: 'test.md',
        content: createMarkdownContent(),
        options: {},
      },
      {
        type: 'write',
        uri: 'test.json',
        content: createJsonContent(),
        options: {},
      },
      {
        type: 'write',
        uri: 'test.bin',
        content: createBinaryContent(),
        options: {},
      },
    ],

    // Delete operations
    delete: uris.map(uri => ({
      type: 'delete',
      uri,
      options: {},
    })),

    // List operations
    list: [
      { type: 'list', pattern: '**', options: {} },
      { type: 'list', pattern: '*.md', options: {} },
      { type: 'list', pattern: 'test/*', options: {} },
    ],
  }
}

// Factory for error cases
export function createErrorScenarios() {
  return {
    // Not found errors
    notFound: {
      type: 'read',
      uri: 'does-not-exist.md',
      expectError: 'ContentNotFoundError',
    },

    // Validation errors
    invalidContent: {
      type: 'write',
      uri: 'invalid.md',
      content: {
        /* Missing required fields */
      },
      expectError: 'ContentValidationError',
    },

    // Access errors
    accessDenied: {
      type: 'write',
      uri: 'forbidden/test.md',
      content: createTestContent(),
      expectError: 'ContentAccessError',
    },

    // Format errors
    invalidFormat: {
      type: 'write',
      uri: 'invalid-json.json',
      content: createTestContent({
        data: '{ invalid json',
        contentType: 'application/json',
      }),
      expectError: 'ContentFormatError',
    },
  }
}

// Usage example in tests
describe('ContentStore operations', () => {
  test('successfully reads and writes content', async () => {
    // Create store
    const store = createContentStore()

    // Get test operation and data
    const operations = createTestOperations(['test.md'])
    const content = createMarkdownContent()

    // Write test content
    await store.write('test.md', content)

    // Read and verify
    const result = await store.read('test.md')
    expect(result).toEqual(content)
  })

  test('handles different content types correctly', async () => {
    // Create store
    const store = createContentStore()

    // Write mixed content types
    const operations = createTestOperations().writeMixed

    for (const op of operations) {
      await store.write(op.uri, op.content)
    }

    // Verify each type
    const markdown = await store.read('test.md')
    expect(markdown.contentType).toBe('text/markdown')

    const json = await store.read('test.json')
    expect(json.contentType).toBe('application/json')

    const binary = await store.read('test.bin')
    expect(binary.contentType).toBe('application/octet-stream')
  })
})

Considerations

  • Create factories for common data structures needed in tests
  • Allow customization through overrides with sensible defaults
  • Create specialized factories for different data variations
  • Use factories to reduce duplication and improve test maintainability

Asynchronous Testing Pattern

Pattern Overview

The Asynchronous Testing pattern validates operations that involve promises, timeouts, and events.

Implementation Example

typescript
import { describe, test, expect, vi } from 'vitest'
import { createMemoryAdapter } from '@lib/content/adapters/common/memory'
import { createContentStore } from '@lib/content/store/factory'

// Setup timer and promise mocking
vi.useFakeTimers()

describe('Asynchronous content operations', () => {
  let adapter
  let store

  beforeEach(() => {
    adapter = createMemoryAdapter()
    store = createContentStore({ adapter })
  })

  // Test async event handling
  test('listen for content changes', async () => {
    // Setup change listener with mock
    const changeListener = vi.fn()
    const unsubscribe = store.onChange(changeListener)

    // Verify no initial calls
    expect(changeListener).not.toHaveBeenCalled()

    // Make a change
    await store.write('test.md', {
      data: 'Test content',
      contentType: 'text/markdown',
    })

    // Verify listener was called
    expect(changeListener).toHaveBeenCalledTimes(1)
    expect(changeListener).toHaveBeenCalledWith(
      'test.md',
      expect.objectContaining({
        data: 'Test content',
        contentType: 'text/markdown',
      }),
      'write'
    )

    // Unsubscribe and verify no more calls
    unsubscribe()
    await store.write('test2.md', {
      data: 'More content',
      contentType: 'text/markdown',
    })

    expect(changeListener).toHaveBeenCalledTimes(1)
  })

  // Test throttling and debouncing
  test('throttled operations respect timing', async () => {
    // Create throttled operation (100ms)
    const throttledWrite = throttle(store.write.bind(store), 100)

    // Execute multiple times in rapid succession
    throttledWrite('test.md', { data: 'A', contentType: 'text/plain' })
    throttledWrite('test.md', { data: 'B', contentType: 'text/plain' })
    throttledWrite('test.md', { data: 'C', contentType: 'text/plain' })

    // Fast-forward time by 50ms (not enough for throttle)
    vi.advanceTimersByTime(50)

    // Only the first call should have executed
    const contentAfter50ms = await store.read('test.md')
    expect(contentAfter50ms.data).toBe('A')

    // Fast-forward another 100ms (enough for another throttled call)
    vi.advanceTimersByTime(100)

    // The last queued call should now have executed
    const contentAfter150ms = await store.read('test.md')
    expect(contentAfter150ms.data).toBe('C')
  })

  // Test concurrent operations
  test('handles concurrent operations correctly', async () => {
    // Start multiple operations concurrently
    const operations = [
      store.write('test1.md', { data: 'Content 1', contentType: 'text/plain' }),
      store.write('test2.md', { data: 'Content 2', contentType: 'text/plain' }),
      store.write('test3.md', { data: 'Content 3', contentType: 'text/plain' }),
      store.read('test1.md').catch(() => null),
    ]

    // Wait for all to complete
    await Promise.all(operations)

    // Verify results
    const results = await Promise.all([
      store.read('test1.md'),
      store.read('test2.md'),
      store.read('test3.md'),
    ])

    expect(results[0].data).toBe('Content 1')
    expect(results[1].data).toBe('Content 2')
    expect(results[2].data).toBe('Content 3')
  })

  // Test timeouts
  test('operations timeout after specified time', async () => {
    // Mock adapter read to delay
    const originalRead = adapter.read
    adapter.read = vi.fn(async uri => {
      // Wait for 5 seconds (longer than timeout)
      await new Promise(resolve => setTimeout(resolve, 5000))
      return originalRead(uri)
    })

    // Create store with short timeout
    const timeoutStore = createContentStore({
      adapter,
      timeout: 1000, // 1 second timeout
    })

    // Start operation
    const operation = timeoutStore.read('test.md')

    // Fast-forward past timeout
    vi.advanceTimersByTime(2000)

    // Verify operation timed out
    await expect(operation).rejects.toThrow('Operation timed out')
  })
})

// Simple throttle implementation for testing
function throttle(fn, delay) {
  let lastCall = 0
  let timeout = null
  let lastArgs = null

  return function throttled(...args) {
    const now = Date.now()

    // Clear any pending timeouts
    if (timeout) {
      clearTimeout(timeout)
      timeout = null
    }

    // If enough time has passed, execute immediately
    if (now - lastCall >= delay) {
      lastCall = now
      return fn(...args)
    }

    // Otherwise, schedule for later
    lastArgs = args
    timeout = setTimeout(
      () => {
        lastCall = Date.now()
        timeout = null
        fn(...lastArgs)
      },
      delay - (now - lastCall)
    )
  }
}

Considerations

  • Control time with fake timers for predictable testing
  • Test concurrent operations with Promise.all and race conditions
  • Verify event listeners are properly registered and cleaned up
  • Use isolation to prevent test interference with async operations

Snapshot Testing Pattern

Pattern Overview

The Snapshot Testing pattern verifies that complex objects match expected structures without manual assertions.

Implementation Example

typescript
import { describe, test, expect } from 'vitest'
import { createContentStore } from '@lib/content/store/factory'
import { createMemoryAdapter } from '@lib/content/adapters/common/memory'
import { withTransformers } from '@lib/content/middleware/transformers'

// Test content transformations with snapshots
describe('Content transformations', () => {
  let store

  beforeEach(async () => {
    // Create store with transformers
    store = createContentStore({
      adapter: createMemoryAdapter(),
      enhancers: [
        withTransformers({
          'text/markdown': [
            // Add transformers for markdown
            extractFrontmatter,
            parseMarkdown,
            enhanceLinks,
          ],
          'application/json': [
            // Add transformers for JSON
            parseJson,
            validateJson,
            addMetadata,
          ],
        }),
      ],
    })

    // Set up test content
    await store.write('document.md', {
      data: `---
title: Test Document
author: Test Author
date: 2025-01-01
---

# Test Document

This is a [test link](https://example.com).
`,
      contentType: 'text/markdown',
    })

    await store.write('data.json', {
      data: `{
  "id": 123,
  "name": "Test Item",
  "properties": {
    "color": "blue",
    "size": "medium"
  }
}`,
      contentType: 'application/json',
    })
  })

  // Test markdown transformation with snapshot
  test('transforms markdown content correctly', async () => {
    // Read and transform content
    const content = await store.read('document.md')

    // Verify transformation result matches snapshot
    expect(content).toMatchSnapshot({
      metadata: expect.any(Object), // Metadata can vary between test runs
    })
  })

  // Test JSON transformation with snapshot
  test('transforms JSON content correctly', async () => {
    // Read and transform content
    const content = await store.read('data.json')

    // Verify transformation result matches snapshot
    expect(content).toMatchSnapshot({
      metadata: {
        ...expect.any(Object),
        processedAt: expect.any(String), // Ignore specific timestamp
      },
    })
  })

  // Test multiple transformations at once
  test('all transformations produce consistent output', async () => {
    // Read all content
    const markdown = await store.read('document.md')
    const json = await store.read('data.json')

    // Create a combined snapshot
    expect({
      markdown: {
        ...markdown,
        metadata: { ...markdown.metadata, processedAt: '[timestamp]' },
      },
      json: {
        ...json,
        metadata: { ...json.metadata, processedAt: '[timestamp]' },
      },
    }).toMatchSnapshot()
  })
})

// Mock transformers
function extractFrontmatter(content) {
  // Simple frontmatter extraction
  const match = content.data.match(/^---\n([\s\S]*?)\n---\n/)

  if (match) {
    const frontmatter = match[1]
    const lines = frontmatter.split('\n')
    const metadata = {}

    lines.forEach(line => {
      const [key, value] = line.split(': ')
      if (key && value) {
        metadata[key.trim()] = value.trim()
      }
    })

    return {
      ...content,
      data: content.data.replace(/^---\n[\s\S]*?\n---\n/, ''),
      metadata: {
        ...content.metadata,
        ...metadata,
      },
    }
  }

  return content
}

function parseMarkdown(content) {
  // Mock markdown parsing (would use a real parser in production)
  return {
    ...content,
    processed: {
      ...content.processed,
      headings: content.data.match(/^#+\s+(.*)$/gm) || [],
      links: (content.data.match(/\[([^\]]+)\]\(([^)]+)\)/g) || []).map(
        link => {
          const match = link.match(/\[([^\]]+)\]\(([^)]+)\)/)
          return { text: match[1], url: match[2] }
        }
      ),
    },
  }
}

function enhanceLinks(content) {
  // Add metadata about links
  return {
    ...content,
    metadata: {
      ...content.metadata,
      linkCount: (content.processed?.links || []).length,
      processedAt: new Date().toISOString(),
    },
  }
}

function parseJson(content) {
  // Parse JSON string to object
  return {
    ...content,
    processed: {
      ...content.processed,
      parsed: JSON.parse(content.data),
    },
  }
}

function validateJson(content) {
  // Simply mark as valid in this test
  return {
    ...content,
    processed: {
      ...content.processed,
      valid: true,
    },
  }
}

function addMetadata(content) {
  // Add metadata based on JSON content
  const parsed = content.processed?.parsed

  return {
    ...content,
    metadata: {
      ...content.metadata,
      keys: Object.keys(parsed || {}),
      hasNested: !!Object.values(parsed || {}).find(v => typeof v === 'object'),
      processedAt: new Date().toISOString(),
    },
  }
}

Considerations

  • Use snapshots for complex object structures that are tedious to assert manually
  • Update snapshots when intentional changes are made
  • Control non-deterministic values (like timestamps) in snapshots
  • Keep snapshots focused on relevant parts of the output

Environment Setup Pattern

Pattern Overview

The Environment Setup pattern establishes predictable test environments.

Implementation Example

typescript
/**
 * Test environment setup for content system tests
 */

import { vi, beforeAll, afterAll, beforeEach, afterEach } from 'vitest'
import { createMemoryAdapter } from '@lib/content/adapters/common/memory'
import { createFilesystemAdapter } from '@lib/content/adapters/node/filesystem'
import { createContentStore } from '@lib/content/store/factory'
import path from 'path'
import fs from 'fs/promises'

// Setup environment variables
beforeAll(() => {
  // Set test environment variables
  process.env.CONTENT_STORE_TYPE = 'memory'
  process.env.CONTENT_STORE_LOG_LEVEL = 'error'
  process.env.TEST_MODE = 'true'

  // Disable console output during tests
  vi.spyOn(console, 'log').mockImplementation(() => {})
  vi.spyOn(console, 'info').mockImplementation(() => {})
})

// Cleanup
afterAll(() => {
  // Restore environment variables
  delete process.env.CONTENT_STORE_TYPE
  delete process.env.CONTENT_STORE_LOG_LEVEL
  delete process.env.TEST_MODE

  // Restore console
  vi.restoreAllMocks()
})

// Global adapter and store instances for memory-based tests
export function createTestMemoryStore(options = {}) {
  const adapter = createMemoryAdapter()
  const store = createContentStore({
    adapter,
    ...options,
  })

  return { adapter, store }
}

// Filesystem-based test store with temporary directory
export async function createTestFilesystemStore(options = {}) {
  // Create temporary directory
  const testDir = path.join(
    process.cwd(),
    'tmp',
    'test',
    `content-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`
  )

  // Ensure directory exists
  await fs.mkdir(testDir, { recursive: true })

  // Create adapter and store
  const adapter = createFilesystemAdapter({
    basePath: testDir,
    ...options.adapter,
  })

  const store = createContentStore({
    adapter,
    ...options.store,
  })

  // Return store with cleanup function
  return {
    adapter,
    store,
    testDir,
    async cleanup() {
      // Dispose store and adapter
      await store.dispose()

      // Remove temporary directory
      await fs.rm(testDir, { recursive: true, force: true })
    },
  }
}

// Example usage in test file
describe('Content operations with filesystem storage', () => {
  let testEnv

  beforeEach(async () => {
    // Create test environment
    testEnv = await createTestFilesystemStore()
  })

  afterEach(async () => {
    // Cleanup test environment
    await testEnv.cleanup()
  })

  test('writes and reads content from filesystem', async () => {
    const { store } = testEnv

    // Write content
    await store.write('test.md', {
      data: '# Test',
      contentType: 'text/markdown',
      metadata: { title: 'Test' },
    })

    // Verify exists on disk
    const filePath = path.join(testEnv.testDir, 'test.md')
    const exists = await fs
      .access(filePath)
      .then(() => true)
      .catch(() => false)

    expect(exists).toBe(true)

    // Read content
    const content = await store.read('test.md')
    expect(content.data).toContain('# Test')
  })
})

Considerations

  • Create isolated environments for each test to prevent cross-test contamination
  • Provide cleanup functions to ensure resources are released
  • Seed test environments with predictable initial state
  • Mock external dependencies for faster and more reliable tests

Released under the MIT License.