Integration vs Unit Test Mocks - Separation Guide
Date: November 7, 2025
Summary
The codebase has TWO separate mock/fixture systems that serve different purposes and should remain separate:
- Integration Test Infrastructure (
tests/integration/setup/) - Unit Test Mocks (
tests/mocks/)
Integration Test Infrastructure
Location: tests/integration/setup/
Purpose: Support end-to-end integration tests that verify complete workflows with real database interactions
Files:
tests/integration/setup/
├── factories/
│ ├── database-fixtures.ts # Create REAL database records
│ └── workos-events.ts # WorkOS event data for integration tests
├── mocks.ts # Mock external APIs (Next.js, WorkOS)
├── fixtures.ts # Test data helpers
└── utilities/ # DB helpers, cleanup utilities
What They Do:
-
database-fixtures.ts: Factory functions that create ACTUAL database records for testing
ensureNodeType()- Insert real node typescreateResourceWithPermission()- Create real graph nodes and edgessetupTestWorkspace()- Create complete test hierarchies in database
-
mocks.ts: Mock EXTERNAL dependencies to prevent real API calls
- Mock Next.js
cookies()to avoid request scope errors - Mock WorkOS client to prevent real API calls
- Mock session context for authenticated test scenarios
- Mock Next.js
-
workos-events.ts: Factory functions for WorkOS webhook event payloads
Testing Approach:
// Integration test example
import { setupTestWorkspace } from '../setup/factories/database-fixtures';
import '../setup/mocks'; // Mock external APIs
describe('Full Workflow', () => {
it('should handle complete user journey', async () => {
// Create REAL database records
const workspace = await setupTestWorkspace(client, {
resourceCount: 3
});
// Test actual API endpoints and workflows
const result = await someRealOperation(workspace.user.id);
expect(result).toBeDefined();
});
});
Unit Test Mocks
Location: tests/mocks/
Purpose: Provide mock objects for isolated unit testing without database or external dependencies
Files:
tests/mocks/
├── db/
│ ├── pool-client.mock.ts # Mock PoolClient objects
│ ├── query-result.fixtures.ts # Mock query results
│ └── presets.ts # Pre-configured mock scenarios
├── auth/ # Auth mocks (to be implemented)
├── services/ # Service mocks (to be implemented)
└── models/ # Model fixtures (to be implemented)
What They Do:
- Provide MOCK objects that simulate behavior without real implementation
- Allow testing in complete isolation
- No database connection required
- Fast execution
Testing Approach:
// Unit test example
import { createPoolClientMock } from '@tests/mocks';
describe('Isolated Function', () => {
it('should process data correctly', () => {
// Use MOCK client (no real database)
const client = createPoolClientMock({
query: vi.fn().mockResolvedValue({ rows: [{ id: 1 }] })
});
// Test function logic in isolation
const result = await someFunction(client);
expect(result).toBe(expected);
});
});
Key Differences
| Aspect | Integration Tests | Unit Tests |
|---|---|---|
| Database | Real PostgreSQL | No database (mocked) |
| Speed | Slower (DB I/O) | Fast (no I/O) |
| Scope | End-to-end workflows | Single function/class |
| Setup | Create real data | Mock objects only |
| Cleanup | Must clean up DB | No cleanup needed |
| External APIs | Mocked (WorkOS, Next.js) | Mocked (everything) |
| Purpose | Verify integration | Verify logic |
Answer to Question: Should We Migrate?
NO - These should remain separate.
Keep in tests/integration/setup/:
✅ factories/database-fixtures.ts - Creates REAL database records
✅ factories/workos-events.ts - WorkOS event data for integration tests
✅ mocks.ts - Mocks external APIs for integration tests
✅ fixtures.ts - Test data helpers
✅ utilities/ - Database cleanup and query helpers
Why keep them separate:
- Different purposes - Integration tests verify workflows, unit tests verify logic
- Different dependencies - Integration needs real DB, unit needs mocks
- Already well-organized - Integration test infrastructure is complete and documented
- No duplication - They serve different testing needs
Add to tests/mocks/ (Unit Tests Only):
🆕 auth/ - Auth service mocks for unit tests
🆕 services/ - Service layer mocks for unit tests
🆕 models/ - Model fixtures for unit tests
✅ db/ - Database client mocks for unit tests (already complete)
When to Use Which
Use Integration Test Infrastructure When:
- Testing complete user workflows
- Verifying database schema/constraints
- Testing graph traversals and permissions
- Validating API endpoints end-to-end
- Testing webhook handlers
- Need real database behavior
Use Unit Test Mocks When:
- Testing individual functions
- Testing class methods in isolation
- Verifying business logic
- Testing error handling
- Fast feedback during development
- No database dependencies needed
Example Scenarios
Scenario 1: Testing Permission Check Logic
Unit Test (use tests/mocks/):
import { createPoolClientMock } from '@tests/mocks';
// Test the checkPermission FUNCTION logic
it('should return true for valid permission', async () => {
const client = createPoolClientMock({
query: vi.fn().mockResolvedValue({
rows: [{ has_permission: true }]
})
});
const result = await checkPermission(client, 'user', 'resource', 'read');
expect(result).toBe(true);
});
Integration Test (use tests/integration/setup/):
import { setupTestWorkspace } from '../setup/factories/database-fixtures';
// Test the COMPLETE permission flow with real database
it('should grant and verify permissions end-to-end', async () => {
const workspace = await setupTestWorkspace(client, {
resourceCount: 1,
resourcePermission: 'read'
});
// This actually queries the database
const result = await checkPermission(
client,
workspace.user.nodeId,
workspace.resources[0].nodeId,
'read'
);
expect(result).toBe(true);
});
Scenario 2: Testing WorkOS Integration
Unit Test (use tests/mocks/):
import { createWorkOSClientMock } from '@tests/mocks'; // To be implemented
// Test service logic that calls WorkOS
it('should handle WorkOS user fetch', async () => {
const workos = createWorkOSClientMock({
userManagement: {
getUser: vi.fn().mockResolvedValue({
id: 'user_123',
email: 'test@example.com'
})
}
});
const service = new UserService(workos);
const user = await service.fetchUser('user_123');
expect(user.email).toBe('test@example.com');
});
Integration Test (use tests/integration/setup/):
import '../setup/mocks'; // Mocks WorkOS API
import { createWorkOSWebhookEvent } from '../setup/factories/workos-events';
// Test complete webhook handling flow
it('should process user.created webhook', async () => {
const event = createWorkOSWebhookEvent('user.created', {
user: {
id: 'user_123',
email: 'test@example.com'
}
});
// This creates real database records
await handleWebhook(event);
// Verify in database
const user = await client.query(
'SELECT * FROM users WHERE external_id = $1',
['user_123']
);
expect(user.rows).toHaveLength(1);
});
Summary
DO NOT MIGRATE integration test infrastructure to tests/mocks/.
The two systems serve different purposes:
- Integration tests verify complete workflows with real database
- Unit tests verify isolated logic with mocks
Keep them separate and continue building out tests/mocks/ for unit testing needs only.
Next Steps
Proceed with implementing Phase 5-8 for unit test mocks only:
- ✅ Phase 5: Auth mocks for unit tests
- ✅ Phase 6: WorkOS mocks for unit tests (different from integration!)
- ✅ Phase 7: Service/model mocks for unit tests
- ✅ Phase 8: Documentation
Integration test infrastructure is complete and should remain as-is in tests/integration/setup/.