Overview
Implement comprehensive end-to-end tests for the query-driven sync feature with on-demand collection loading (PR #763, RFC #676). These tests will verify that predicate push-down, deduplication, pagination, joins, and other distributed system behaviors work correctly across different collection types and syncModes.
Related Links:
These new e2e tests will branch off PR #763 and be merged into it.
Goals
- Create a shared e2e test suite that can be reused across different collection implementations
- Test critical distributed systems scenarios: concurrent loadSubset calls, race conditions, deduplication
- Verify predicate push-down works correctly with various query patterns
- Test pagination, ordering, and multi-collection joins
- Catch integration bugs like those found during early testing (see Known Bugs section)
- Keep execution time under 5 minutes for CI/CD
Architecture
Package Structure
Create a new package: @tanstack/db-collection-e2e
This package will export:
- Individual test scenario groups organized by feature
- Standard seed data schema and fixtures
- Utility functions for common assertions
- Configuration types/interfaces
Test Organization
Tests will be organized into feature-based scenario groups:
- Predicates Suite - Basic where clause functionality
- Pagination Suite - Order by, limit, offset, setWindow
- Joins Suite - Single and multi-collection joins
- Deduplication Suite - Concurrent loadSubset scenarios
- Collation Suite - String comparison configuration
- Mutations Suite - Data mutations with on-demand mode
- Live Updates Suite (optional) - Reactive updates for sync-enabled collections
- Regression Suite - Explicit tests for known bugs
Collection Integration
Each collection package (e.g., electric-db-collection, query-db-collection) will:
- Import the shared test suite
- Provide collection instances configured for both eager and on-demand syncModes
- Implement collection-specific setup/teardown hooks
- Choose which optional test suites to run
Example structure:
packages/
db-collection-e2e/ # Shared test suite package
src/
suites/
predicates.test.ts
pagination.test.ts
joins.test.ts
deduplication.test.ts
collation.test.ts
mutations.test.ts
live-updates.test.ts (optional)
regressions.test.ts
fixtures/
seed-data.ts
test-schema.ts
utils/
assertions.ts
types.ts
index.ts
electric-db-collection/
e2e/
setup.ts # Docker, Postgres, Electric setup
electric.e2e.test.ts # Imports and runs shared suites
query-db-collection/
e2e/
setup.ts # Mock backend setup
query.e2e.test.ts # Imports and runs shared suites
Standard Test Data Schema
Entities and Relationships
Design a schema that exercises edge cases across various data types:
// Users table
interface User {
id: string; // UUID
name: string; // For collation testing
email: string | null; // Nullable field
age: number; // Numeric comparisons
isActive: boolean; // Boolean predicates
createdAt: Date; // Date comparisons
metadata: object | null; // JSON field (if supported)
deletedAt: Date | null; // Soft delete pattern
}
// Posts table
interface Post {
id: string;
userId: string; // Foreign key to User
title: string;
content: string | null;
viewCount: number;
publishedAt: Date | null;
deletedAt: Date | null;
}
// Comments table
interface Comment {
id: string;
postId: string; // Foreign key to Post
userId: string; // Foreign key to User
text: string;
createdAt: Date;
deletedAt: Date | null;
}
Seed Data Volume
- Users: ~100 records
- Posts: ~100 records (distributed across users)
- Comments: ~100 records (distributed across posts)
This provides enough data to test pagination effectively while keeping tests fast.
Data Distribution
Ensure data includes:
- Mix of null and non-null values
- Various string cases for collation testing (uppercase, lowercase, special chars)
- Date ranges (past, present, future)
- Boolean distributions (true/false/null if applicable)
- Numeric ranges (negative, zero, positive, large numbers)
- Some soft-deleted records (deletedAt not null)
Test Configuration Interface
interface E2ETestConfig {
// Collection instances configured for testing
collections: {
eager: {
users: Collection<User>;
posts: Collection<Post>;
comments: Collection<Comment>;
};
onDemand: {
users: Collection<User>;
posts: Collection<Post>;
comments: Collection<Comment>;
};
};
// Lifecycle hooks
setup: () => Promise<void>;
teardown: () => Promise<void>;
// Per-test hooks (optional)
beforeEach?: () => Promise<void>;
afterEach?: () => Promise<void>;
}
Note: Collections handle their own mutation logic internally, so no separate mutator is needed in the config.
Test Scenario Groups
1. Predicates Suite
Test basic predicate functionality across all data types:
Test cases:
eq() with various types (string, number, boolean, date, UUID, null)
ne() with various types
gt(), gte(), lt(), lte() with numbers and dates
in() with arrays
isNull() and isNotNull()
- Complex boolean logic (AND, OR combinations)
- Nested predicates
Assertions:
- Query returns correct data matching predicates
- Only necessary data is loaded (check collection state)
- No errors thrown
2. Pagination Suite
Test ordering, limits, offsets, and window management:
Test cases:
- Basic orderBy (ascending/descending)
- Multiple orderBy fields
- Limit without offset
- Limit with offset
liveQuery.utils.setWindow() - changing windows
setWindow() while data is loading
- Overlapping windows (page 2 before page 1 completes)
- Edge cases: limit=0, offset beyond dataset, negative values
Assertions:
- Correct page of data returned
- Proper ordering maintained
- Only requested data loaded (not entire dataset)
3. Joins Suite
Test multi-collection joins with various syncMode combinations:
Test cases:
- Two-collection join (Users + Posts)
- Three-collection join (Users + Posts + Comments)
- Mixed syncModes: one on-demand, one eager
- Both collections on-demand
- Predicates on joined collections (verify pushdown)
- Ordering across joined collections
- Pagination on joined results
setWindow() on joins requiring loads from multiple collections
Assertions:
- Correct joined data returned
- Each collection only loads required subset (predicate pushdown working)
- Query result matches expected join
- No extra data loaded
Example test:
// Join users and posts where userId = 123
// Verify only user 123's posts loaded, not all posts
4. Deduplication Suite
Test concurrent loadSubset calls and deduplication behavior:
Test cases:
- Two queries with identical predicates calling loadSubset simultaneously
- Overlapping predicates (one is subset of another)
- Queries arriving while data is still loading
- Multiple concurrent queries with different predicates
- Deduplication with limit/offset variations
Assertions:
- Use deduplication callback to count actual vs deduplicated loads
- Verify expected number of backend requests
- All queries receive correct data
- No race conditions or data corruption
5. Collation Suite
Test string collation configuration:
Test cases:
- Default collation behavior
- Custom
defaultStringCollation at collection level
- Custom collation at query level
- Collation inheritance in nested queries
- String comparisons with different collations (case-sensitive vs case-insensitive)
Assertions:
- String predicates respect collation settings
- Correct data returned based on collation
- Query-level collation overrides collection-level
6. Mutations Suite
Test mutations with on-demand syncMode:
Test cases:
- Mutate loaded data (verify sync back)
- Create new record (verify appears in matching queries)
- Update record to match/unmatch query predicates
- Delete record
- Concurrent mutations
Assertions:
- Mutations to loaded records sync correctly
- Query results update reactively after mutations
- Cannot mutate records that aren't loaded (verify error/behavior)
Note: Mutation logic is collection-specific, handled internally.
7. Live Updates Suite (Optional)
For collections that support sync, test reactive updates:
Test cases:
- Load subset via query
- Mutate data on backend (outside client)
- Verify query reactively updates
- Updates during active loadSubset
- Multiple queries watching same data
Assertions:
- Query data updates when backend changes
- Updates don't trigger unnecessary reloads
- Correct data maintained throughout
8. Regression Suite
Explicit tests for known bugs found during development:
Critical bugs to test:
-
Missing subset_ params bug
- Query with no
where or limit should include proper params
- Server shouldn't treat as normal shape request
- Test: Create query without predicates, verify URL params
-
eq(deletedAt, null) SQL syntax error
- Test:
eq(deletedAt, null) should work without SQL errors
- Verify correct records returned
-
eq(id, uuid) syntax error
- Test:
eq(id, '{uuid-value}') should work
- Verify UUID fields work in predicates
-
Unnecessary offset in URL
- Verify offset only added when needed
- Test URL construction
-
JSON parse inefficiency
- Ensure snapshot responses aren't parsed multiple times
- (May need internal inspection or performance monitoring)
9. Progressive Mode (Deferred)
Not included in initial implementation, but placeholder for future:
- Test mode transition from on-demand → eager when full sync completes
- Verify subset loads work while background sync proceeds
Infrastructure Setup
Docker Orchestration
Follow Electric's e2e pattern (see generated docs):
Services needed:
- PostgreSQL (port 54321)
- Electric server (port 3000)
- tmpfs for performance
- Health checks with proper timeouts
docker-compose.yml example:
services:
postgres:
image: postgres:14-alpine
environment:
POSTGRES_DB: electric
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
ports:
- "54321:5432"
tmpfs: /var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 2s
timeout: 10s
retries: 5
electric:
image: electricsql/electric:latest
environment:
DATABASE_URL: postgresql://postgres:password@postgres:5432/electric
ELECTRIC_WRITE_TO_PG_MODE: direct_writes
PG_PROXY_PORT: "65432"
ports:
- "3000:3000"
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
interval: 2s
timeout: 10s
retries: 5
Database Isolation
Use schema-based isolation with unique table names per test:
// Shared database: electric_test schema
// Unique table names: `table_for_{taskId}_{randomSuffix}`
This prevents test collisions while allowing parallel execution within Vitest's constraints.
Vitest Configuration
Critical settings:
export default defineConfig({
test: {
fileParallelism: false, // Serial execution for shared DB
globalSetup: './e2e/global-setup.ts',
timeout: 30000, // Extended for Docker operations
}
})
Global Setup
Implement health check for Docker services:
// global-setup.ts
export async function setup() {
// Wait for Postgres and Electric to be healthy
await waitForHealthCheck('http://localhost:3000/health')
await waitForPostgres('postgresql://postgres:password@localhost:54321/electric')
}
Test Fixtures
Use Vitest's test.extend() for composable fixtures:
const testWithDb = test.extend({
db: async ({}, use) => {
const db = await setupDatabase()
await use(db)
await cleanupDatabase(db)
}
})
const testWithCollections = testWithDb.extend({
collections: async ({ db }, use) => {
const collections = await seedAndCreateCollections(db)
await use(collections)
}
})
Test Flow Pattern
Typical test structure:
test('should load correct data with predicates', async ({ collections }) => {
// 1. Seed data happens in fixture setup
// 2. Create query with predicates
const query = collections.onDemand.users.liveQuery({
where: eq(users.age, 25)
})
// 3. Await preload
await query.preload()
// 4. Assert correct data
const result = query.getResult()
expect(result).toHaveLength(expectedCount)
expect(result.every(u => u.age === 25)).toBe(true)
// 5. Verify only necessary data loaded
const loadedIds = getLoadedUserIds(collections.onDemand.users)
expect(loadedIds).toEqual(expectedUserIds)
})
Mock Backend (Query Collection)
For query-db-collection, mock the backend fetch:
// Mock TanStack Query backend
const mockBackend = {
fetchUsers: vi.fn(async ({ where, orderBy, limit, offset }) => {
// Return filtered/paginated seed data
return filterData(seedData.users, { where, orderBy, limit, offset })
})
}
Implementation Checklist
Phase 1: Infrastructure
Phase 2: Core Test Suites
Phase 3: Additional Suites
Phase 4: Collection Integration
Phase 5: CI/CD
Success Criteria
Future Enhancements
- Progressive mode transition testing
- Performance benchmarks
- Subscription lifecycle edge cases (unsubscribe during load)
- Network failure scenarios
- More complex join patterns (self-joins, multiple paths)
References
Notes for Implementation
- Start small: Implement infrastructure + Predicates Suite first to validate approach
- Copy Electric patterns: Leverage proven patterns from Electric's e2e setup
- Keep tests strict: No retries, fast timeouts - rely on setup hooks for consistency
- Test through public API: Don't expose internal state unless necessary
- Document as you go: Add examples for future collections to reference
Overview
Implement comprehensive end-to-end tests for the query-driven sync feature with on-demand collection loading (PR #763, RFC #676). These tests will verify that predicate push-down, deduplication, pagination, joins, and other distributed system behaviors work correctly across different collection types and syncModes.
Related Links:
~/programs/electric/packages/typescript-client(see generated docs in this repo)These new e2e tests will branch off PR #763 and be merged into it.
Goals
Architecture
Package Structure
Create a new package:
@tanstack/db-collection-e2eThis package will export:
Test Organization
Tests will be organized into feature-based scenario groups:
Collection Integration
Each collection package (e.g.,
electric-db-collection,query-db-collection) will:Example structure:
Standard Test Data Schema
Entities and Relationships
Design a schema that exercises edge cases across various data types:
Seed Data Volume
This provides enough data to test pagination effectively while keeping tests fast.
Data Distribution
Ensure data includes:
Test Configuration Interface
Note: Collections handle their own mutation logic internally, so no separate mutator is needed in the config.
Test Scenario Groups
1. Predicates Suite
Test basic predicate functionality across all data types:
Test cases:
eq()with various types (string, number, boolean, date, UUID, null)ne()with various typesgt(),gte(),lt(),lte()with numbers and datesin()with arraysisNull()andisNotNull()Assertions:
2. Pagination Suite
Test ordering, limits, offsets, and window management:
Test cases:
liveQuery.utils.setWindow()- changing windowssetWindow()while data is loadingAssertions:
3. Joins Suite
Test multi-collection joins with various syncMode combinations:
Test cases:
setWindow()on joins requiring loads from multiple collectionsAssertions:
Example test:
4. Deduplication Suite
Test concurrent loadSubset calls and deduplication behavior:
Test cases:
Assertions:
5. Collation Suite
Test string collation configuration:
Test cases:
defaultStringCollationat collection levelAssertions:
6. Mutations Suite
Test mutations with on-demand syncMode:
Test cases:
Assertions:
Note: Mutation logic is collection-specific, handled internally.
7. Live Updates Suite (Optional)
For collections that support sync, test reactive updates:
Test cases:
Assertions:
8. Regression Suite
Explicit tests for known bugs found during development:
Critical bugs to test:
Missing subset_ params bug
whereorlimitshould include proper paramseq(deletedAt, null) SQL syntax error
eq(deletedAt, null)should work without SQL errorseq(id, uuid) syntax error
eq(id, '{uuid-value}')should workUnnecessary offset in URL
JSON parse inefficiency
9. Progressive Mode (Deferred)
Not included in initial implementation, but placeholder for future:
Infrastructure Setup
Docker Orchestration
Follow Electric's e2e pattern (see generated docs):
Services needed:
docker-compose.yml example:
Database Isolation
Use schema-based isolation with unique table names per test:
This prevents test collisions while allowing parallel execution within Vitest's constraints.
Vitest Configuration
Critical settings:
Global Setup
Implement health check for Docker services:
Test Fixtures
Use Vitest's
test.extend()for composable fixtures:Test Flow Pattern
Typical test structure:
Mock Backend (Query Collection)
For
query-db-collection, mock the backend fetch:Implementation Checklist
Phase 1: Infrastructure
@tanstack/db-collection-e2epackagePhase 2: Core Test Suites
Phase 3: Additional Suites
Phase 4: Collection Integration
electric-db-collectionquery-db-collectionPhase 5: CI/CD
Success Criteria
electric-db-collectionandquery-db-collectionFuture Enhancements
References
README_E2E_TESTS.md,ELECTRIC_E2E_PATTERNS.md, etc. in this repo~/programs/electric/packages/typescript-clientNotes for Implementation