Troubleshooting Integration Tests
Version: 1.0
Date: 2025-11-07
Audience: All Developers
Table of Contents
- Common Errors
- Database Issues
- Fixture Problems
- Performance Issues
- CI/CD Failures
- Debugging Techniques
- Getting Help
Common Errors
Error: "database does not exist"
Full error:
Error: database "test_db" does not exist
Causes:
- Test database was not created
- Database was already cleaned up
- Wrong database name in connection string
- Test tried to connect before database creation completed
Solutions:
// ✅ SOLUTION 1: Verify database creation order
test('correct order', async ({ asSystem }) => {
// 1. First create database
await asSystem(async (client) => {
await client.query('CREATE DATABASE test_db');
});
// 2. Then connect to it
const pool = new Pool({ database: 'test_db' });
// ...
});
// ✅ SOLUTION 2: Add existence check
test('check before connect', async ({ asSystem }) => {
const exists = await asSystem(async (client) => {
const result = await client.query(`
SELECT 1 FROM pg_database WHERE datname = $1
`, ['test_db']);
return result.rows.length > 0;
});
if (!exists) {
throw new Error('Test database not created');
}
});
Prevention:
- Always create database before connecting
- Use unique database names to avoid conflicts
- Add proper error handling in fixtures
Error: "connection refused" / "ECONNREFUSED"
Full error:
Error: connect ECONNREFUSED 127.0.0.1:5432
Causes:
- PostgreSQL is not running
- Wrong host/port configuration
- Firewall blocking connection
- PostgreSQL listening on different address
Solutions:
# Check if PostgreSQL is running
pg_isready -h localhost -p 5432
# Start PostgreSQL (macOS with Homebrew)
brew services start postgresql@14
# Start PostgreSQL (Linux systemd)
sudo systemctl start postgresql
# Check PostgreSQL status
brew services list # macOS
sudo systemctl status postgresql # Linux
# Check what port PostgreSQL is listening on
psql -h localhost -p 5432 -U postgres -c "SHOW port;"
Environment check:
# Verify environment variables
echo $POSTGRES_HOST
echo $POSTGRES_PORT
echo $DATABASE_URL
# Test manual connection
psql -h localhost -p 5432 -U postgres -d postgres
In tests:
test('diagnose connection', async ({ asSystem }) => {
try {
await asSystem(async (client) => {
const result = await client.query('SELECT version()');
console.log('PostgreSQL version:', result.rows[0].version);
});
} catch (error) {
console.error('Connection failed:', error);
console.log('Check:');
console.log('1. Is PostgreSQL running? (pg_isready)');
console.log('2. Environment variables set? (.env.test)');
console.log('3. Correct host/port?');
throw error;
}
});
Prevention:
- Add PostgreSQL to startup items
- Document setup in team wiki
- Add connection health check to test setup
- Use docker-compose for consistent environment
Error: "permission denied for schema"
Full error:
Error: permission denied for schema public
Causes:
- User doesn't have schema permissions
- Wrong role being used
- RLS policies blocking access
- Missing GRANT statements
Solutions:
// Check current user and permissions
test('check permissions', async ({ asSystem }) => {
await asSystem(async (client) => {
// Check current user
const user = await client.query('SELECT current_user');
console.log('Current user:', user.rows[0].current_user);
// Check schema permissions
const perms = await client.query(`
SELECT
schema_name,
schema_owner,
has_schema_privilege(current_user, schema_name, 'USAGE') as can_use,
has_schema_privilege(current_user, schema_name, 'CREATE') as can_create
FROM information_schema.schemata
WHERE schema_name NOT LIKE 'pg_%'
AND schema_name != 'information_schema'
`);
console.log('Schema permissions:', perms.rows);
});
});
// Grant necessary permissions
test('grant permissions', async ({ asSystem }) => {
await asSystem(async (client) => {
await client.query(`
GRANT USAGE ON SCHEMA public TO studio_user;
GRANT CREATE ON SCHEMA public TO studio_user;
`);
});
});
Prevention:
- Ensure migration files include proper GRANT statements
- Use
asSystemfor infrastructure setup - Document required permissions in setup guide
Error: "relation does not exist"
Full error:
Error: relation "public.users" does not exist
Causes:
- Table not created yet
- Migrations not run
- Wrong schema name
- Transaction rolled back
Solutions:
// Check if table exists
test('verify table exists', async ({ asSystem }) => {
const exists = await asSystem(async (client) => {
const result = await client.query(`
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'users'
)
`);
return result.rows[0].exists;
});
if (!exists) {
throw new Error('Table "users" does not exist. Run migrations?');
}
});
// List available tables
test('list tables', async ({ asSystem }) => {
const tables = await asSystem(async (client) => {
const result = await client.query(`
SELECT schemaname, tablename
FROM pg_tables
WHERE schemaname NOT LIKE 'pg_%'
AND schemaname != 'information_schema'
ORDER BY schemaname, tablename
`);
return result.rows;
});
console.log('Available tables:', tables);
});
Prevention:
- Run migrations before tests
- Add table existence checks in test setup
- Use proper schema qualification (e.g.,
public.users)
Error: "fixture lifecycle error"
Full error:
Error: Fixture "myFixture" was not properly cleaned up
Causes:
- Missing
use()call in fixture - Exception thrown before cleanup
- Incorrect fixture structure
- Missing try/finally
Solutions:
// ❌ WRONG - Missing use() call
const badFixture = async ({}, use) => {
const resource = await createResource();
// Missing await use(resource)!
await cleanup(resource);
};
// ✅ CORRECT - Proper fixture structure
const goodFixture = async ({}, use) => {
const resource = await createResource();
try {
await use(resource); // Must call use()!
} finally {
await cleanup(resource); // Always runs
}
};
// ✅ CORRECT - Simple fixture
const simpleFixture = async ({}, use) => {
const resource = await createResource();
await use(resource);
await cleanup(resource); // Runs even if test fails
};
Prevention:
- Always call
await use(resource) - Use try/finally for guaranteed cleanup
- Test fixture cleanup with failing tests
Error: "too many clients"
Full error:
Error: sorry, too many clients already
Causes:
- Connection pool exhausted
- Connections not being returned
- Tests running in parallel without limits
- Connection leaks in code
Solutions:
// Check active connections
test('check connections', async ({ asSystem }) => {
const conns = await asSystem(async (client) => {
const result = await client.query(`
SELECT
datname,
count(*) as connections,
max(now() - query_start) as longest_query
FROM pg_stat_activity
WHERE datname IS NOT NULL
GROUP BY datname
ORDER BY connections DESC
`);
return result.rows;
});
console.log('Active connections:', conns);
});
// Limit parallel tests in vitest.config.ts
export default defineConfig({
test: {
maxConcurrency: 5, // Limit parallel tests
fileParallelism: false, // Run test files sequentially
}
});
Prevention:
- Always return connections to pool
- Use fixtures for automatic cleanup
- Limit test parallelism
- Monitor connection count
Database Issues
Connection Pool Exhaustion
Symptoms:
- Tests hang waiting for connections
- "too many clients" errors
- Slow test execution
Diagnosis:
test('diagnose pool exhaustion', async ({ asSystem }) => {
// Check pool status
await asSystem(async (client) => {
const result = await client.query(`
SELECT
count(*) as total_connections,
count(*) FILTER (WHERE state = 'active') as active,
count(*) FILTER (WHERE state = 'idle') as idle,
count(*) FILTER (WHERE state = 'idle in transaction') as idle_in_transaction
FROM pg_stat_activity
WHERE datname = current_database()
`);
console.log('Connection pool status:', result.rows[0]);
// Idle in transaction is bad - indicates connection leak
if (parseInt(result.rows[0].idle_in_transaction) > 0) {
console.warn('⚠️ Connections stuck in transaction!');
}
});
});
Solutions:
- Reduce test parallelism
- Increase pool size (if appropriate)
- Find and fix connection leaks
- Use fixtures that guarantee cleanup
Transaction Deadlocks
Symptoms:
Error: deadlock detected
DETAIL: Process 1234 waits for ShareLock on transaction 5678
Causes:
- Two tests modifying same data in different order
- Parallel tests with insufficient isolation
Solutions:
// ✅ Use row-level locking
test('avoid deadlock', async ({ asUser }) => {
await asUser('user-123', 'org-456', async (client) => {
await client.query('BEGIN');
// Lock rows in consistent order (by ID)
await client.query(`
SELECT * FROM items
WHERE id IN ($1, $2)
ORDER BY id -- Consistent order prevents deadlock
FOR UPDATE
`, [id1, id2]);
// Update rows
await client.query('UPDATE items SET ... WHERE id = $1', [id1]);
await client.query('UPDATE items SET ... WHERE id = $2', [id2]);
await client.query('COMMIT');
});
});
// ✅ Use per-test isolation
test('use unique data', async ({ asUser }) => {
// Each test creates its own data
const uniqueId = `test_${Date.now()}_${Math.random()}`;
// No conflicts with other tests
});
Prevention:
- Lock rows in consistent order
- Use per-test data isolation
- Reduce test parallelism
- Use shorter transactions
Migration Failures
Symptoms:
- SQL syntax errors during migration
- Tables not created
- Constraints failing
Diagnosis:
test('debug migration', async ({ emptyDatabase }) => {
try {
// Run migrations one at a time
for (const file of migrationFiles) {
console.log(`Running ${file}...`);
await runMigrationFile(emptyDatabase.client, file);
console.log(`✓ ${file} completed`);
}
} catch (error) {
console.error('Migration failed:', error);
// Check what was created
const tables = await emptyDatabase.client.query(`
SELECT schemaname, tablename
FROM pg_tables
WHERE schemaname NOT LIKE 'pg_%'
ORDER BY schemaname, tablename
`);
console.log('Tables created so far:', tables.rows);
throw error;
}
});
Common issues:
- Missing semicolons - Ensure each statement ends with
; - Dependency order - Create tables before foreign keys
- Idempotency - Use
IF NOT EXISTSclauses - Syntax errors - Test SQL files individually with
psql
Cleanup Problems
Symptoms:
- Test databases not being deleted
- Orphaned test data
- Disk space filling up
Manual cleanup:
-- Find test databases
SELECT datname
FROM pg_database
WHERE datname LIKE 'test_%'
ORDER BY datname;
-- Terminate all connections to test databases
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname LIKE 'test_%'
AND pid <> pg_backend_pid();
-- Drop test databases
DO $$
DECLARE
db_name TEXT;
BEGIN
FOR db_name IN
SELECT datname FROM pg_database WHERE datname LIKE 'test_%'
LOOP
EXECUTE 'DROP DATABASE IF EXISTS ' || quote_ident(db_name);
RAISE NOTICE 'Dropped database: %', db_name;
END LOOP;
END $$;
Automated cleanup:
// Add to test setup
afterAll(async () => {
// Cleanup any remaining test databases
const pool = new Pool({
host: process.env.POSTGRES_HOST,
user: process.env.POSTGRES_USER,
password: process.env.POSTGRES_PASSWORD,
database: 'postgres',
});
const client = await pool.connect();
try {
const result = await client.query(`
SELECT datname FROM pg_database
WHERE datname LIKE 'test_%'
AND age(now(), (SELECT stats_reset FROM pg_stat_database WHERE datname = datname)) > interval '1 hour'
`);
for (const row of result.rows) {
await client.query(`
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = $1
`, [row.datname]);
await client.query(`DROP DATABASE IF EXISTS ${row.datname}`);
console.log(`Cleaned up stale test database: ${row.datname}`);
}
} finally {
client.release();
await pool.end();
}
});
Fixture Problems
Fixture Not Available
Error:
test('example', async ({ myFixture }) => {
// Error: Property 'myFixture' does not exist
});
Causes:
- Fixture not exported from test.ts
- Using base test instead of extended test
- Typo in fixture name
- Wrong test import
Solutions:
// ✅ CORRECT - Import extended test
import test from './test'; // Extended test with fixtures
test('uses fixture', async ({ myFixture }) => {
// myFixture is available
});
// ❌ WRONG - Import base test
import { test } from 'vitest'; // Base test without fixtures
test('missing fixture', async ({ myFixture }) => {
// Error: myFixture doesn't exist on base test
});
Fixture Dependency Errors
Error:
Error: Circular fixture dependency detected
Cause:
// ❌ Circular dependency
const test = baseTest.extend({
fixtureA: async ({ fixtureB }, use) => {
await use(await createA(fixtureB));
},
fixtureB: async ({ fixtureA }, use) => {
await use(await createB(fixtureA));
},
});
Solution:
// ✅ Resolve dependency order
const test = baseTest.extend({
// Base fixture (no dependencies)
database: async ({}, use) => {
const db = await createDatabase();
await use(db);
},
// Depends on database
fixtureA: async ({ database }, use) => {
await use(await createA(database));
},
// Depends on fixtureA
fixtureB: async ({ fixtureA }, use) => {
await use(await createB(fixtureA));
},
});
Cleanup Not Running
Problem:
test('cleanup issue', async ({ myFixture }) => {
// Test fails here
expect(false).toBe(true);
// Cleanup after use() should still run, but doesn't seem to
});
Common causes:
- Missing try/finally:
// ❌ Cleanup may not run on error
myFixture: async ({}, use) => {
const resource = await create();
await use(resource);
await cleanup(resource); // Skipped if use() throws
}
// ✅ Cleanup always runs
myFixture: async ({}, use) => {
const resource = await create();
try {
await use(resource);
} finally {
await cleanup(resource); // Always runs
}
}
- Test timeout:
// ✅ Increase timeout if cleanup is slow
test('slow cleanup', async ({ myFixture }) => {
// Test code
}, { timeout: 30000 }); // 30 second timeout
Performance Issues
Slow Tests
Symptoms:
- Test suite takes minutes to complete
- Individual tests taking >1 second
Profiling:
test('profile test', async ({ asSystem }) => {
const start = Date.now();
// Setup
const setupStart = Date.now();
await setupTestData();
console.log('Setup:', Date.now() - setupStart, 'ms');
// Query 1
const q1Start = Date.now();
await asSystem(async (client) => {
await client.query('SELECT * FROM large_table');
});
console.log('Query 1:', Date.now() - q1Start, 'ms');
// Query 2
const q2Start = Date.now();
await asSystem(async (client) => {
await client.query('SELECT * FROM another_table WHERE ...');
});
console.log('Query 2:', Date.now() - q2Start, 'ms');
console.log('Total:', Date.now() - start, 'ms');
});
Common solutions:
// ✅ Add indexes for test queries
beforeAll(async ({ asSystem }) => {
await asSystem(async (client) => {
await client.query(`
CREATE INDEX IF NOT EXISTS idx_items_status
ON items(status)
`);
});
});
// ✅ Use smaller test datasets
const testData = items.slice(0, 10); // Not all 10,000 items
// ✅ Batch operations
await client.query(`
INSERT INTO items (name) VALUES
($1), ($2), ($3)
`, [name1, name2, name3]);
// Instead of:
await client.query('INSERT INTO items (name) VALUES ($1)', [name1]);
await client.query('INSERT INTO items (name) VALUES ($1)', [name2]);
await client.query('INSERT INTO items (name) VALUES ($1)', [name3]);
Memory Leaks
Symptoms:
- Tests slow down over time
- Memory usage increases
- "JavaScript heap out of memory"
Diagnosis:
# Run with heap profiler
node --expose-gc --max-old-space-size=4096 node_modules/vitest/vitest.mjs
# Monitor memory in tests
test('check memory', async () => {
const memBefore = process.memoryUsage();
// Run test operations
await heavyOperation();
// Force GC if available
if (global.gc) {
global.gc();
}
const memAfter = process.memoryUsage();
const leakMB = (memAfter.heapUsed - memBefore.heapUsed) / 1024 / 1024;
if (leakMB > 50) {
console.warn(`⚠️ Possible memory leak: ${leakMB.toFixed(2)} MB`);
}
});
Common causes:
- Not closing database connections
- Large objects in test scope
- Event listeners not removed
- Circular references
Connection Leaks
Symptoms:
- Connection pool fills up
- Tests hang waiting for connections
- Database has many idle connections
Detection:
test('detect connection leak', async ({ asSystem }) => {
const countBefore = await asSystem(async (client) => {
const result = await client.query(`
SELECT count(*) FROM pg_stat_activity
WHERE datname = current_database()
`);
return parseInt(result.rows[0].count);
});
// Run test operations
await testOperations();
const countAfter = await asSystem(async (client) => {
const result = await client.query(`
SELECT count(*) FROM pg_stat_activity
WHERE datname = current_database()
`);
return parseInt(result.rows[0].count);
});
if (countAfter > countBefore) {
console.warn(`⚠️ Connection leak: ${countAfter - countBefore} connections`);
}
});
Solutions:
- Use fixtures that guarantee cleanup
- Always call
client.release()orpool.end() - Use try/finally blocks
CI/CD Failures
Tests Pass Locally, Fail in CI
Common causes:
- Environment differences:
// Check environment in CI
test('diagnose CI environment', async ({ asSystem }) => {
console.log('Node version:', process.version);
console.log('Platform:', process.platform);
console.log('Environment:', {
POSTGRES_HOST: process.env.POSTGRES_HOST,
POSTGRES_PORT: process.env.POSTGRES_PORT,
NODE_ENV: process.env.NODE_ENV,
});
await asSystem(async (client) => {
const result = await client.query('SELECT version()');
console.log('PostgreSQL:', result.rows[0].version);
});
});
- Timing issues:
// ❌ Flaky - depends on timing
test('flaky test', async ({ asSystem }) => {
await startBackgroundProcess();
await asSystem(async (client) => {
// Background process might not be ready yet
const result = await client.query('SELECT * FROM processed_items');
expect(result.rows.length).toBeGreaterThan(0);
});
});
// ✅ Robust - wait for condition
test('robust test', async ({ asSystem }) => {
await startBackgroundProcess();
// Wait for background process to complete
await waitFor(async () => {
const result = await asSystem(async (client) => {
return await client.query('SELECT COUNT(*) FROM processed_items');
});
return parseInt(result.rows[0].count) > 0;
}, { timeout: 5000 });
});
- Resource constraints:
# .gitlab-ci.yml or similar
test:
script:
- pnpm test
variables:
# Reduce parallelism for CI
VITEST_MAX_CONCURRENCY: 3
# Increase timeouts
VITEST_TEST_TIMEOUT: 60000
Flaky Tests
Definition: Tests that sometimes pass, sometimes fail
Common causes:
- Non-deterministic data:
// ❌ Flaky - uses NOW()
test('flaky timestamp', async ({ asSystem }) => {
await asSystem(async (client) => {
await client.query(`
INSERT INTO events (timestamp) VALUES (NOW())
`);
const result = await client.query(`
SELECT * FROM events
WHERE timestamp > NOW() - interval '1 second'
`);
expect(result.rows.length).toBe(1); // Might fail!
});
});
// ✅ Deterministic - fixed timestamp
test('stable timestamp', async ({ asSystem }) => {
const fixedTime = '2024-01-01T00:00:00Z';
await asSystem(async (client) => {
await client.query(`
INSERT INTO events (timestamp) VALUES ($1)
`, [fixedTime]);
const result = await client.query(`
SELECT * FROM events WHERE timestamp = $1
`, [fixedTime]);
expect(result.rows.length).toBe(1); // Always works
});
});
- Race conditions:
// ❌ Flaky - parallel tests interfere
test('flaky count', async ({ asSystem }) => {
const result = await asSystem(async (client) => {
return await client.query('SELECT COUNT(*) FROM items');
});
expect(result.rows[0].count).toBe('5'); // Other tests may add items
});
// ✅ Stable - uses isolated data
test('stable count', async ({ asSystem }) => {
const testId = `test_${Date.now()}`;
await asSystem(async (client) => {
await client.query('INSERT INTO items (id) VALUES ($1)', [testId]);
});
const result = await asSystem(async (client) => {
return await client.query('SELECT COUNT(*) FROM items WHERE id = $1', [testId]);
});
expect(result.rows[0].count).toBe('1'); // Only our item
});
Debugging Techniques
Enable Debug Logging
// Enable PostgreSQL query logging
test('with query logging', async ({ asSystem }) => {
await asSystem(async (client) => {
// Enable query logging
await client.query('SET log_statement = \'all\'');
await client.query('SET log_duration = on');
// Your queries - will be logged
await client.query('SELECT * FROM items');
// Reset
await client.query('RESET log_statement');
await client.query('RESET log_duration');
});
});
// Vitest debug mode
// Run with: DEBUG=* pnpm test
import debug from 'debug';
const log = debug('test:mytest');
test('with debug', async ({ asSystem }) => {
log('Starting test');
await asSystem(async (client) => {
log('Executing query');
await client.query('SELECT 1');
log('Query complete');
});
});
Using console.log Effectively
test('effective logging', async ({ asSystem }) => {
console.log('\n=== Test: effective logging ===');
// Log inputs
const input = { userId: 'user-123', action: 'create' };
console.log('Input:', JSON.stringify(input, null, 2));
// Log intermediate results
const result = await asSystem(async (client) => {
console.log('Executing query...');
const r = await client.query('SELECT * FROM items WHERE user_id = $1', [input.userId]);
console.log(`Found ${r.rows.length} items`);
return r;
});
// Log outputs
console.log('Output:', JSON.stringify(result.rows, null, 2));
console.log('=== Test complete ===\n');
});
VSCode Debugging
Add to .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Vitest Tests",
"runtimeExecutable": "pnpm",
"runtimeArgs": [
"test",
"--run",
"--threads",
"false",
"${file}"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
}
]
}
Set breakpoints in VSCode, then press F5 to debug.
Database Query Logging
// Log all queries during test
import { Pool } from 'pg';
const pool = new Pool({
// ... connection config
});
// Intercept queries
const originalQuery = pool.query.bind(pool);
pool.query = function(...args) {
console.log('Query:', args[0]);
if (args[1]) {
console.log('Params:', args[1]);
}
return originalQuery(...args);
};
Getting Help
Before Asking for Help
- Check this guide - Review relevant sections above
- Read error messages carefully - They often contain the solution
- Search existing issues - Someone may have had the same problem
- Isolate the problem - Create minimal reproduction
Creating a Minimal Reproduction
// Minimal test that reproduces the issue
import test from './test';
test('minimal reproduction', async ({ asSystem }) => {
// Simplest possible code that shows the problem
await asSystem(async (client) => {
const result = await client.query('SELECT 1');
console.log(result.rows);
});
});
Information to Include
When reporting an issue:
- Error message - Full stack trace
- Environment - OS, Node version, PostgreSQL version
- Code - Minimal reproduction
- Expected vs actual - What should happen vs what does happen
- Steps to reproduce - Exact commands to run
Reporting Issues
Use the /reportbug command with:
Problem: [Brief description]
Error: [Full error message]
Code: [Minimal reproduction]
Environment:
- OS: macOS 13.0
- Node: v18.17.0
- PostgreSQL: 14.9
- Vitest: 1.0.0
Steps to reproduce