-- Create test database
CREATE DATABASE myapp_test;
-- Test isolation with transactions
/*
beforeEach(async () => {
await db.query('BEGIN');
});
afterEach(async () => {
await db.query('ROLLBACK');
});
test('creates user', async () => {
await db.query('INSERT INTO users (username) VALUES ($1)', ['test']);
const result = await db.query('SELECT * FROM users WHERE username = $1', ['test']);
expect(result.rows).toHaveLength(1);
}); // Rolled back automatically
*/
-- Test fixtures (seed data)
CREATE TABLE test_users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE,
email VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Load fixtures
TRUNCATE test_users CASCADE;
INSERT INTO test_users (id, username, email) VALUES
(1, 'alice', 'alice@test.com'),
(2, 'bob', 'bob@test.com'),
(3, 'carol', 'carol@test.com');
-- Reset sequence
SELECT setval('test_users_id_seq', (SELECT MAX(id) FROM test_users));
-- Factory function
CREATE OR REPLACE FUNCTION create_test_user(
username_prefix TEXT DEFAULT 'user'
)
RETURNS test_users AS $$
DECLARE
new_user test_users;
BEGIN
INSERT INTO test_users (username, email)
VALUES (
username_prefix || '_' || floor(random() * 1000000),
username_prefix || '@test.com'
)
RETURNING * INTO new_user;
RETURN new_user;
END;
$$ LANGUAGE plpgsql;
-- Usage
SELECT * FROM create_test_user('testuser');
-- Clean database helper
CREATE OR REPLACE FUNCTION truncate_all_tables()
RETURNS VOID AS $$
DECLARE
table_name TEXT;
BEGIN
FOR table_name IN
SELECT tablename
FROM pg_tables
WHERE schemaname = 'public'
LOOP
EXECUTE format('TRUNCATE TABLE %I CASCADE', table_name);
END LOOP;
END;
$$ LANGUAGE plpgsql;
-- Reset database for tests
SELECT truncate_all_tables();
-- Test data builder pattern
CREATE OR REPLACE FUNCTION build_test_order(
p_user_id INT DEFAULT NULL,
p_total DECIMAL DEFAULT 99.99,
p_status VARCHAR DEFAULT 'pending'
)
RETURNS orders AS $$
DECLARE
v_user_id INT;
new_order orders;
BEGIN
IF p_user_id IS NULL THEN
v_user_id := (SELECT id FROM create_test_user('order_user'));
ELSE
v_user_id := p_user_id;
END IF;
INSERT INTO orders (user_id, total, status)
VALUES (v_user_id, p_total, p_status)
RETURNING * INTO new_order;
RETURN new_order;
END;
$$ LANGUAGE plpgsql;
-- Create order with defaults
SELECT * FROM build_test_order();
-- Create order with specific values
SELECT * FROM build_test_order(1, 199.99, 'completed');
-- Assert functions for tests
CREATE OR REPLACE FUNCTION assert_equals(
expected ANYELEMENT,
actual ANYELEMENT,
message TEXT DEFAULT 'Assertion failed'
)
RETURNS VOID AS $$
BEGIN
IF expected IS DISTINCT FROM actual THEN
RAISE EXCEPTION '% - Expected: %, Actual: %',
message, expected, actual;
END IF;
END;
$$ LANGUAGE plpgsql;
-- Test example
DO $$
DECLARE
user_count INT;
BEGIN
SELECT COUNT(*) INTO user_count FROM test_users;
PERFORM assert_equals(3, user_count, 'User count should be 3');
END $$;
-- Snapshot testing (save and compare)
CREATE TABLE test_snapshots (
test_name VARCHAR(100) PRIMARY KEY,
snapshot JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Save snapshot
INSERT INTO test_snapshots (test_name, snapshot)
VALUES (
'user_list',
(SELECT jsonb_agg(row_to_json(u.*)) FROM test_users u)
);
-- Compare snapshot
DO $$
DECLARE
current_data JSONB;
saved_snapshot JSONB;
BEGIN
SELECT jsonb_agg(row_to_json(u.*)) INTO current_data
FROM test_users u;
SELECT snapshot INTO saved_snapshot
FROM test_snapshots
WHERE test_name = 'user_list';
IF current_data IS DISTINCT FROM saved_snapshot THEN
RAISE EXCEPTION 'Snapshot mismatch for user_list';
END IF;
END $$;
-- Parallel test databases
/*
// Node.js example - create database per worker
const workerId = process.env.JEST_WORKER_ID || 1;
const testDb = `myapp_test_${workerId}`;
beforeAll(async () => {
await adminClient.query(`DROP DATABASE IF EXISTS ${testDb}`);
await adminClient.query(`CREATE DATABASE ${testDb}`);
await adminClient.query(`
GRANT ALL PRIVILEGES ON DATABASE ${testDb} TO myapp_user
`);
// Run migrations
await runMigrations(testDb);
});
afterAll(async () => {
await adminClient.query(`DROP DATABASE ${testDb}`);
});
*/
-- Test migrations
-- Keep separate test migration table
CREATE TABLE test_schema_migrations (
version VARCHAR(255) PRIMARY KEY,
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Test slow queries
SET statement_timeout = '100ms';
-- This should fail if slow
SELECT * FROM large_table WHERE unindexed_column = 'value';
RESET statement_timeout;
-- Test constraints
DO $$
BEGIN
-- Should succeed
INSERT INTO users (username, email)
VALUES ('testuser', 'test@example.com');
-- Should fail (duplicate email)
BEGIN
INSERT INTO users (username, email)
VALUES ('another', 'test@example.com');
RAISE EXCEPTION 'Expected unique constraint violation';
EXCEPTION
WHEN unique_violation THEN
NULL; -- Expected
END;
END $$;
-- Test triggers
CREATE TABLE audit_test_log (
id SERIAL PRIMARY KEY,
event TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE OR REPLACE FUNCTION test_audit_trigger()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO audit_test_log (event)
VALUES (TG_OP || ' on ' || TG_TABLE_NAME);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER test_audit
AFTER INSERT OR UPDATE OR DELETE ON test_users
FOR EACH ROW
EXECUTE FUNCTION test_audit_trigger();
-- Test the trigger
INSERT INTO test_users (username, email) VALUES ('trigger_test', 'test@example.com');
-- Verify audit log
SELECT * FROM audit_test_log WHERE event = 'INSERT on test_users';
-- Mock/stub pattern
-- Replace function with mock during test
CREATE OR REPLACE FUNCTION send_email(recipient TEXT, subject TEXT)
RETURNS BOOLEAN AS $$
BEGIN
-- Production: Actually send email
-- Test: Log to table instead
INSERT INTO email_log (recipient, subject)
VALUES (recipient, subject);
RETURN TRUE;
END;
$$ LANGUAGE plpgsql;
-- Test data generators
CREATE OR REPLACE FUNCTION random_string(length INT)
RETURNS TEXT AS $$
SELECT string_agg(
substring('abcdefghijklmnopqrstuvwxyz', (random() * 26)::INT + 1, 1),
''
)
FROM generate_series(1, length);
$$ LANGUAGE SQL;
CREATE OR REPLACE FUNCTION random_email()
RETURNS TEXT AS $$
SELECT random_string(10) || '@test.com';
$$ LANGUAGE SQL;
-- Generate bulk test data
INSERT INTO test_users (username, email)
SELECT
'user_' || i,
'user_' || i || '@test.com'
FROM generate_series(1, 1000) i;
-- Performance testing
\timing on
SELECT COUNT(*) FROM test_users WHERE email LIKE '%test.com';
\timing off
-- Coverage: Check all tables have tests
SELECT
t.tablename,
EXISTS (
SELECT 1 FROM test_snapshots
WHERE test_name LIKE '%' || t.tablename || '%'
) AS has_test
FROM pg_tables t
WHERE t.schemaname = 'public'
ORDER BY has_test, t.tablename;
-- Integration test pattern
DO $$
DECLARE
v_user test_users;
v_order orders;
BEGIN
-- Arrange
v_user := create_test_user('integration');
-- Act
v_order := build_test_order(v_user.id, 150.00, 'pending');
UPDATE orders SET status = 'completed' WHERE id = v_order.id;
-- Assert
PERFORM assert_equals(
'completed',
(SELECT status FROM orders WHERE id = v_order.id),
'Order should be completed'
);
END $$;