Testing Guide
This document provides comprehensive guidance for running, maintaining, and developing tests in the MakeItMakeSense.io application.
Overview
The application uses a multi-layered testing strategy:
- Unit Tests - Test individual components and services in isolation
- Mocked Integration Tests - Test API interactions with mocked dependencies
- Real Database Integration Tests - Test complete functionality with real Dgraph database
- End-to-End Tests - Test complete user workflows in the browser
Test Suites
1. Unit Tests (api/__tests__/unit/
)
Purpose: Test individual functions, classes, and components in isolation.
Location: api/__tests__/unit/
Key Test Files:
services/tenantManager.test.ts
- TenantManager functionalityservices/nodeEnrichment.test.ts
- Node enrichment logicservices/schemaRegistry.test.ts
- Schema registry operationsservices/validation.test.ts
- Input validationutils/dgraphAdmin.test.ts
- Dgraph admin utilitiesutils/pushSchema.test.ts
- Schema push functionalitymiddleware/auth.test.ts
- Authentication middleware
Running Unit Tests:
cd api
npm test -- --testPathPattern="unit"
Characteristics:
- Fast execution (< 1 second per test)
- No external dependencies
- Mock all external services
- Focus on business logic validation
2. Mocked Integration Tests (api/__tests__/integration/
)
Purpose: Test API endpoints and request/response flows with mocked database operations.
Location: api/__tests__/integration/
Key Test Files:
endpoints.test.ts
- Basic API endpoint testinghierarchy.test.ts
- Hierarchy CRUD operationsgraphql.test.ts
- GraphQL query and mutation testingintegration.test.ts
- Cross-feature integration scenarios
Running Mocked Integration Tests:
cd api
npm test -- --testPathPattern="integration" --testPathIgnorePatterns="integration-real"
Characteristics:
- Mock
adaptiveTenantFactory
and database operations - Fast execution (1-3 seconds per test)
- Test API contracts and validation logic
- No real database required
- Suitable for CI/CD pipelines
Mock Pattern:
// Standard mocking pattern used in integration tests
jest.mock('../../services/adaptiveTenantFactory', () => ({
adaptiveTenantFactory: {
createTenant: jest.fn().mockResolvedValue(mockTenantClient),
createTestTenant: jest.fn().mockResolvedValue(mockTenantClient),
// ...
}
}));
3. Real Database Integration Tests (api/__tests__/integration-real/
)
Purpose: Test complete functionality with real Dgraph database and tenant infrastructure.
Location: api/__tests__/integration-real/
Key Test Files:
basic-crud.test.ts
- Complete CRUD operations in real test tenantnamespace-isolation.test.ts
- Tenant isolation verificationhierarchy-operations.test.ts
- Real hierarchy managementgraphql-operations.test.ts
- Advanced GraphQL operations and performancediagnostic.test.ts
- System connectivity and capability detection
Prerequisites:
- Complete development environment setup (see Complete Setup Guide)
- Dgraph Enterprise with multi-tenant configuration enabled
- Test tenant namespace properly configured
Running Real Integration Tests:
cd api
npm test -- --testPathPattern="integration-real"
For detailed environment setup and troubleshooting, see Complete Setup Guide
Characteristics:
- No mocking - uses real
adaptiveTenantFactory
and database - Real namespace isolation testing
- Slower execution (5-10 seconds per test)
- Tests production code paths
- Requires live Dgraph Enterprise instance
- Provides highest confidence in system behavior
Test Helper Pattern:
// Real integration tests use actual tenant infrastructure
import { testRequest, verifyInTestTenant } from '../helpers/realTestHelpers';
// testRequest automatically adds X-Tenant-Id: test-tenant header
const response = await testRequest(app)
.post('/api/query')
.send({ query: '...' })
.expect(200);
// verifyInTestTenant uses real adaptiveTenantFactory for verification
const verification = await verifyInTestTenant('query { ... }');
4. Frontend Tests (frontend/tests/
)
Purpose: Test React components, hooks, and user interactions.
Location: frontend/tests/
Test Categories:
unit/
- Component and utility function testsintegration/
- Component integration scenariose2e/
- End-to-end browser testing with Playwright
Running Frontend Tests:
cd frontend
# Unit and integration tests
npm test
# End-to-end tests
npm run test:e2e
Test Data Management
Mocked Tests
- Use
api/__tests__/helpers/mockData.ts
for consistent test data - All data is in-memory and automatically cleaned up
- No database interaction required
Real Integration Tests
- Use
api/__tests__/helpers/realTestHelpers.ts
utilities - Operate in dedicated test tenant namespace (0x1)
- Automatic cleanup via
beforeEach
andafterAll
hooks - Isolated from production data
Test Data Lifecycle:
describe('Real Integration Test Suite', () => {
beforeAll(async () => {
// Initialize test tenant with schema and basic setup
await global.testUtils.setupTestDatabase();
});
afterAll(async () => {
// Clean up test tenant data
await global.testUtils.cleanupTestDatabase();
});
beforeEach(async () => {
// Reset test tenant to known state before each test
await global.testUtils.resetTestDatabase();
});
});
Test Configuration
Jest Configuration (api/jest.config.js
)
module.exports = {
testEnvironment: 'node',
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
testMatch: ['**/__tests__/**/*.test.js'],
collectCoverageFrom: [
'routes/**/*.js',
'services/**/*.js',
'utils/**/*.js',
'middleware/**/*.js'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
};
Test Setup (api/jest.setup.js
)
- Configures test environment constants
- Provides global test utilities
- Sets up test tenant configuration
- Initializes test database helpers
Best Practices
Writing Tests
-
Test Naming:
// Good: Descriptive test names
it('should create node with hierarchy assignment in test tenant', async () => {
// Bad: Vague test names
it('should work', async () => { -
Test Structure (AAA Pattern):
it('should validate node type against level restrictions', async () => {
// Arrange
const nodeData = createTestNodeData({ type: 'invalidType' });
// Act
const response = await testRequest(app)
.post('/api/mutate')
.send({ mutation: '...' });
// Assert
expect(response.status).toBe(400);
expect(response.body.error).toContain('type not allowed');
}); -
Test Independence:
- Each test should be independent and self-contained
- Use proper setup/teardown to ensure clean state
- Don't rely on execution order
-
Assertion Quality:
// Good: Specific assertions
expect(response.body.addNode.node[0].id).toBe(nodeData.id);
expect(response.body.addNode.node[0].hierarchyAssignments).toHaveLength(1);
// Bad: Vague assertions
expect(response.body).toBeTruthy();
Mocking Best Practices
Axios Mocking
For HTTP client mocking, use jest.spyOn
with proper setup in beforeEach
:
describe('HTTP Service Tests', () => {
let mockedAxiosPost: jest.SpyInstance;
beforeEach(() => {
jest.clearAllMocks();
// Set up the spy in beforeEach to ensure it's applied after module loading
mockedAxiosPost = jest.spyOn(axios, 'post').mockImplementation();
});
afterEach(() => {
mockedAxiosPost.mockRestore();
});
it('should make HTTP request', async () => {
const mockResponse = { status: 200, data: { success: true } };
mockedAxiosPost.mockResolvedValueOnce(mockResponse);
const result = await httpService.makeRequest();
expect(result.success).toBe(true);
expect(mockedAxiosPost).toHaveBeenCalledWith(/* expected args */);
});
});
Key Points:
- Use
jest.spyOn
instead ofjest.mock()
for more reliable mocking - Set up spies in
beforeEach
to ensure proper timing - Always restore mocks in
afterEach
- Avoid manual mocks in
__mocks__/
directories when usingjest.spyOn
Test Performance
-
Use Appropriate Test Types:
- Unit tests for business logic
- Mocked integration for API contracts
- Real integration for critical paths
- E2E for user workflows
-
Optimize Real Integration Tests:
- Group related operations in single tests when appropriate
- Use efficient database cleanup strategies
- Minimize test data creation
-
Parallel Execution:
// Configure Jest for parallel execution
module.exports = {
maxWorkers: '50%', // Use half of available CPU cores
testTimeout: 30000 // 30 second timeout for real integration tests
};
Continuous Integration
GitHub Actions Configuration
# Example CI configuration for multi-layered testing
name: Test Suite
on: [push, pull_request]
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: cd api && npm ci
- run: cd api && npm test -- --testPathPattern="unit"
mocked-integration:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: cd api && npm ci
- run: cd api && npm test -- --testPathPattern="integration" --testPathIgnorePatterns="integration-real"
# Real integration tests require Dgraph Enterprise setup (optional in CI)
real-integration:
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
services:
dgraph:
image: dgraph/dgraph:latest # Would need Enterprise image
ports:
- 8080:8080
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: cd api && npm ci
- run: cd api && npm test -- --testPathPattern="integration-real"
Debugging Tests
Common Issues
-
Real Integration Test Failures:
# Check Dgraph connectivity
curl http://localhost:8080/health
# Verify namespace support
curl http://localhost:8080/state
# Run diagnostic test
cd api && npm test -- --testPathPattern="diagnostic.test.ts" -
Timeout Issues:
// Increase timeout for slow operations
it('should handle large batch operations', async () => {
// Test implementation
}, 30000); // 30 second timeout -
Mock Issues:
// Clear mocks between tests
beforeEach(() => {
jest.clearAllMocks();
}); -
Axios Mocking Issues:
- Use
jest.spyOn
instead ofjest.mock()
- Set up spies in
beforeEach
for proper timing - Avoid conflicts between manual mocks and
jest.mock()
- Use
Test Output Analysis
# Run with verbose output
npm test -- --verbose
# Run with coverage
npm test -- --coverage
# Run specific test file
npm test -- basic-crud.test.ts
# Run tests matching pattern
npm test -- --testNamePattern="should create node"
Coverage Goals
Current Coverage Targets
- Unit Tests: 90%+ coverage of services and utilities
- Integration Tests: 80%+ coverage of API endpoints
- Real Integration: 100% coverage of critical multi-tenant paths
Coverage Reports
cd api
npm test -- --coverage --coverageDirectory=coverage-reports
Troubleshooting
Test Environment Issues
- Port conflicts: Ensure test ports don't conflict with running services
- Database state: Use proper cleanup to avoid test interference
- Environment variables: Verify all required variables are set
Performance Issues
- Slow tests: Profile and optimize database operations
- Memory leaks: Check for proper cleanup of resources
- Timeouts: Adjust timeout values for complex operations
This testing guide ensures comprehensive coverage while maintaining development velocity and deployment confidence. Regular updates to this guide should reflect changes in testing strategy and new test patterns.