Testing & Development
One of WhoDB's most powerful features for developers is the ability to generate realistic mock data and quickly test database interactions. Whether you're writing integration tests, developing new features, or debugging database issues, WhoDB streamlines your testing workflows.
Tip
This guide covers mock data generation, query testing, and development workflows that accelerate your development cycle.
Development Database Workflow
Setting Up Your Development Database
1 Connect to Development Database
First, connect WhoDB to your development or staging database—not production.
Use separate connection profiles for:
Development : Local or cloud-hosted dev database
Staging : Production-like environment for testing
Backups : Read-only mirrors for analysis
Never test on production databases directly.
Navigate to tables you're working with and verify their current state.
Look for:
Are there existing records?
What's the current max ID or sequence number?
Are there any constraints that affect testing?
This context is important for generating realistic test data.
3 Document Constraints and Rules
For each table you'll test with, understand:
Foreign key dependencies
NOT NULL constraints
UNIQUE constraints
DEFAULT values
Data type limits
This affects what mock data you can generate.
Generating Mock Data
The Mock Data Generator
WhoDB includes a powerful mock data generator for creating test datasets:
Open the table/header context menu and choose the mock data action, or press Cmd/Ctrl + Shift + G while the table has focus.
This opens the configuration interface for test data generation.
2 Configure Generation Options
Set up how many rows you want to generate:
Choose your data handling mode:
Append : Add mock data to existing records (safe, reversible)
Overwrite : Replace all existing data (use with caution on development only)
For most testing scenarios, append mode is safer because you can delete the generated data if something goes wrong.
Specify how many rows to generate:
Different scenarios need different data volumes:
The current backend limit is 200 rows per mock-data generation run.
Click "Generate" to create the mock data.
The generator creates:
Realistic column values : Names, emails, dates, numbers
Foreign key relationships : Valid references to related tables
Constraint compliance : Respecting NOT NULL and UNIQUE constraints
Diverse data : Multiple variations to test different code paths
After generation, verify the data was created correctly by viewing the table.
Mock Data Generation Patterns
Pattern 1: Bootstrap a Complete Schema
For a brand new feature, populate all related tables at once:
1 Generate in Dependency Order
If you have foreign key dependencies, generate in order:
Parent tables first (users, categories)
Middle tables (orders, products)
Junction tables (user_roles, product_tags)
Transaction tables (payments, events)
This ensures foreign key constraints are satisfied.
Parent tables: 10-50 rows (users, companies)
Transaction tables: 100-500 rows (orders, events)
Detail tables: Same as parent (order items = orders)
This creates realistic data distribution.
Generate each table in append mode so they accumulate naturally.
For repeated testing cycles, you can clear and regenerate using overwrite mode.
Pattern 2: Test Specific Scenarios
Generate data that exercises particular code paths:
Example: Testing discount code functionality
1. Generate 50 users (append)
2. Generate 30 orders with various amounts
3. Generate 5 different discount codes
4. Manually update some orders to use discount codes
5. Test your discount logic against this dataset
Use a mix of generated data and manual entries for edge cases.
Pattern 3: Reproduce Production Issues
When debugging production issues locally:
Use your analysis queries to export a sample of real production data (safely anonymized).
SELECT * FROM orders
WHERE created_at > NOW ( ) - INTERVAL '7 days'
LIMIT 100 ;
Import the data or use it as a template for mock data generation.
This gives you realistic data to test against.
Generate additional records to test edge cases you want to verify.
Testing Workflows
Workflow 1: Unit Testing with Mock Data
When writing unit tests that interact with a database:
Generate a consistent set of mock data before each test run.
beforeEach ( async ( ) => {
await generateMockData ( 'users' , { count : 5 , mode : 'append' } ) ;
await generateMockData ( 'orders' , { count : 10 , mode : 'append' } ) ;
} ) ;
afterEach ( async ( ) => {
await deleteTestData ( ) ;
} ) ;
This ensures tests start with known data state.
Verify your application can Create, Read, Update, Delete records:
test ( 'can create new order' , async ( ) => {
const newOrder = await createOrder ( { user_id : 1 , amount : 99.99 } ) ;
expect ( newOrder . id ) . toBeDefined ( ) ;
} ) ;
test ( 'can update order status' , async ( ) => {
await updateOrder ( 1 , { status : 'shipped' } ) ;
} ) ;
Use WhoDB to verify edge case handling:
test ( 'handles NULL values correctly' , async ( ) => {
} ) ;
test ( 'handles large numbers correctly' , async ( ) => {
} ) ;
After each test, quickly check results in WhoDB:
Browse the data to confirm test operations succeeded, or filter to isolate test records.
Workflow 2: Integration Testing
When testing how multiple components interact:
1 Set Up Complex Data State
Generate mock data representing realistic application state:
Users table : 30 diverse user records
Products table : 20 products across categories
Orders table : 100 orders from the users
Order items table : Multiple items per order
Reviews table : Some products reviewed multiple times
This creates a rich dataset for integration testing.
Test complex workflows like:
Placing an order and verifying inventory decreases
Updating user profile and seeing changes reflected in order history
Calculating totals correctly across multiple orders
Applying discounts and verifying calculations
After each operation, verify results in WhoDB:
Use filters to isolate test records and confirm state changed correctly.
3 Test Cross-Table Consistency
Use queries to verify data consistency:
SELECT oi . order_id , oi . product_id
FROM order_items oi
LEFT JOIN products p ON oi . product_id = p . id
WHERE p . id IS NULL ;
SELECT o . id , o . total , SUM ( oi . quantity * oi . price )
FROM orders o
LEFT JOIN order_items oi ON o . id = oi . order_id
GROUP BY o . id
HAVING o . total != SUM ( oi . quantity * oi . price ) ;
Run these queries in Scratchpad after your integration tests to verify data integrity.
Workflow 3: Performance Testing
When optimizing queries or testing with large datasets:
1 Generate Realistic Data Volume
Create dataset sizes matching your production scale:
If production has 10K users, generate 10K test users
If production has 1M orders, generate 100K-1M test orders
Adjust down to test performance at smaller scales
Generate in stages: 100 rows → 1K rows → 10K rows to see where performance degrades.
2 Write Performance Test Queries
In Scratchpad, write the queries your application will execute:
SELECT COUNT ( * ) FROM users WHERE status = 'active' ;
SELECT u . * , COUNT ( o . id ) as order_count
FROM users u
LEFT JOIN orders o ON u . id = o . user_id
WHERE u . created_at > '2024-01-01'
GROUP BY u . id ;
SELECT * FROM products
WHERE name ILIKE '%laptop%' OR description ILIKE '%laptop%' ;
Execute these and check the plan with EXPLAIN ANALYZE to measure timing.
3 Compare Before/After Optimization
For query optimization:
Before : Execute query with current code, note time
Optimize : Add index, rewrite query, etc.
After : Execute query with optimization, note time
Compare : Calculate performance improvement
SELECT * FROM orders WHERE user_id = 123 ;
CREATE INDEX idx_orders_user_id ON orders ( user_id ) ;
SELECT * FROM orders WHERE user_id = 123 ;
Document performance improvements for your team.
4 Load Test Your Application
Simulate concurrent users:
const queries = Array ( 100 ) . fill (
'SELECT * FROM orders WHERE user_id = ?'
) ;
const start = Date . now ( ) ;
await Promise . all ( queries ) ;
const elapsed = Date . now ( ) - start ;
console . log ( ` 100 queries completed in ${ elapsed } ms ` ) ;
console . log ( ` Average: ${ elapsed / 100 } ms per query ` ) ;
Monitor database performance while load tests run:
Spot-check query timing with EXPLAIN ANALYZE in Scratchpad
Check connection counts and locks with your database's system views
Verify no deadlocks occur
Query Testing and Debugging
Testing Query Logic
When writing complex queries, test them iteratively:
Begin with basic queries and build complexity:
SELECT * FROM users LIMIT 5 ;
SELECT * FROM users
WHERE created_at > '2024-01-01'
LIMIT 5 ;
SELECT
created_at:: date ,
COUNT ( * ) as user_count
FROM users
WHERE created_at > '2024-01-01'
GROUP BY created_at:: date ;
SELECT
u . id ,
u . username ,
COUNT ( o . id ) as order_count
FROM users u
LEFT JOIN orders o ON u . id = o . user_id
WHERE u . created_at > '2024-01-01'
GROUP BY u . id
ORDER BY order_count DESC ;
Build incrementally, verifying results at each step.
2 Verify Results Make Sense
After each query, ask:
Do row counts make sense?
Do values look reasonable?
Are there unexpected NULLs?
Do aggregates add up correctly?
Review the result grid carefully—errors often jump out visually.
Once your query works for normal data, test edge cases:
SELECT u . * , COUNT ( o . id ) as order_count
FROM users u
LEFT JOIN orders o ON u . id = o . user_id
GROUP BY u . id ;
Write the same query multiple ways and compare results:
SELECT u . id , COUNT ( o . id ) as order_count
FROM users u
LEFT JOIN orders o ON u . id = o . user_id
GROUP BY u . id ;
SELECT u . id , ( SELECT COUNT ( * ) FROM orders WHERE user_id = u . id ) as order_count
FROM users u ;
SELECT DISTINCT u . id ,
COUNT ( * ) OVER ( PARTITION BY u . id ) as order_count
FROM users u
LEFT JOIN orders o ON u . id = o . user_id ;
Understanding these differences makes you a better SQL developer.
Debugging Failing Queries
When a query doesn't return what you expect:
If a complex query fails, identify which part:
SELECT u . id , u . name , COUNT ( o . id ) as order_count
FROM users u
LEFT JOIN orders o ON u . id = o . user_id
WHERE u . created_at > '2024-01-01'
AND o . total > 100
GROUP BY u . id
HAVING COUNT ( o . id ) > 5 ;
SELECT COUNT ( * ) FROM users WHERE created_at > '2024-01-01' ;
SELECT COUNT ( * ) FROM orders WHERE total > 100 ;
SELECT u . id , o . id
FROM users u
LEFT JOIN orders o ON u . id = o . user_id
WHERE u . created_at > '2024-01-01'
AND o . total > 100
LIMIT 10 ;
SELECT u . id , COUNT ( o . id ) as order_count
FROM users u
LEFT JOIN orders o ON u . id = o . user_id
WHERE u . created_at > '2024-01-01'
AND o . total > 100
GROUP BY u . id ;
This isolation helps identify exactly which part is causing issues.
Verify your assumptions about the data:
SELECT COUNT ( * ) FROM orders WHERE user_id IS NULL ;
SELECT user_id , COUNT ( * ) as cnt FROM users GROUP BY user_id HAVING COUNT ( * ) > 1 ;
SELECT * FROM orders WHERE total <= 0 ;
Many query failures result from incorrect assumptions about data.
3 Use EXPLAIN for Performance
When a query runs slowly:
EXPLAIN ANALYZE
SELECT u . * , COUNT ( o . id ) as order_count
FROM users u
LEFT JOIN orders o ON u . id = o . user_id
GROUP BY u . id
ORDER BY order_count DESC ;
The output shows:
How many rows the database expects to process
What indexes are being used
Where time is being spent
Opportunities for optimization
Development Workflows with Mock Data
Workflow: Feature Development Cycle
1 Start: Set Up Test Environment
Generate initial mock data for your feature:
User data (30 rows)
Feature-specific data (100 rows)
Edge case data (10-20 rows)
SELECT status , COUNT ( * ) FROM users GROUP BY status ;
2 Develop: Write Your Feature Code
Build your feature against the mock data:
Your code can query freely without affecting production
You can modify test data as you discover new requirements
You can regenerate completely if you make mistakes
3 Test: Verify Against Data
After each coding session:
Run your feature
Check results in WhoDB
Verify data state changed correctly
Test edge cases
const order = await getOrder ( 1 ) ;
const discounted = await applyDiscount ( order , 'SUMMER20' ) ;
4 Debug: Use Queries for Investigation
When something unexpected happens, write queries to understand why:
SELECT id , original_total , discount_code , new_total ,
( original_total - new_total ) as savings
FROM orders
WHERE discount_code = 'SUMMER20'
ORDER BY created_at DESC
LIMIT 5 ;
5 Iterate: Regenerate and Retry
If you need fresh test data:
Export current test results (for documentation)
Delete all test records
Regenerate fresh mock data
Retry the development cycle
Before committing:
Delete all test mock data
Verify production (or staging) is unaffected
Run full test suite
Deploy changes with confidence
Query Examples for Common Development Tasks
Monitoring and Debugging
SELECT * FROM error_logs
ORDER BY created_at DESC
LIMIT 20 ;
SELECT * FROM transactions
WHERE status = 'failed'
AND created_at > NOW ( ) - INTERVAL '1 hour'
ORDER BY created_at DESC ;
SELECT table_name , COUNT ( * ) as activity_count
FROM audit_log
WHERE created_at > NOW ( ) - INTERVAL '1 hour'
GROUP BY table_name
ORDER BY activity_count DESC ;
Data Validation
SELECT o . id FROM orders o
LEFT JOIN users u ON o . user_id = u . id
WHERE u . id IS NULL ;
SELECT email , COUNT ( * ) as cnt
FROM users
GROUP BY email
HAVING COUNT ( * ) > 1 ;
SELECT table_name , MAX ( created_at ) as last_record
FROM audit_log
GROUP BY table_name
ORDER BY last_record ;
Performance Investigation
SELECT query , avg_execution_time , execution_count
FROM query_log
ORDER BY avg_execution_time DESC
LIMIT 20 ;
SELECT table_name , row_count , size_mb
FROM table_stats
ORDER BY size_mb DESC ;
SELECT table_name , access_count
FROM table_access_stats
ORDER BY access_count DESC ;
Best Practices for Development Testing
Always Use a Separate Development Database
Automate Test Data Generation
Use Realistic Data Volumes
Version Control Your Test Queries
Export Before Major Changes
Next Steps
Ready to advance your development practices?
Learn how to explore unfamiliar databases
Use WhoDB for advanced data analysis
Master advanced SQL techniques
Learn comprehensive export capabilities
Check
You now have a complete testing and development workflow. From generating realistic mock data through integrated testing with performance validation, you have tools to accelerate development and confidence in your database code. The combination of mock data generation, interactive querying, and rapid iteration creates a developer experience that catches bugs early and speeds up development cycles.