Basic Testing
Learn the fundamentals of testing with @rageltd/bun-test-utils. This guide covers the core patterns and utilities for effective testing in Bun projects.
Prerequisites: This guide assumes familiarity with
bun:test and basic testing concepts.
Test Setup
Start every test file with proper setup to ensure clean test isolation:
import { describe, it, expect, beforeEach, afterEach, afterAll } from 'bun:test';
import {
setupTestCleanup,
createModuleMocker,
clearMockRegistry
} from '@rageltd/bun-test-utils';
// Setup automatic cleanup (call once per test file)
setupTestCleanup();
const mockModules = createModuleMocker();
describe('My Test Suite', () => {
beforeEach(() => {
// Setup test-specific mocks here
});
afterEach(() => {
// Additional cleanup if needed
clearMockRegistry();
});
afterAll(() => {
// Restore all module mocks
mockModules.restoreAll();
});
// Your tests here
});
Module Mocking
Mock entire modules to isolate units under test:
import { createModuleMocker } from '@rageltd/bun-test-utils';
const mockModules = createModuleMocker();
describe('Service Tests', () => {
beforeEach(async () => {
// Mock external dependencies
await mockModules.mock('@/services/api', () => ({
apiClient: {
get: () => Promise.resolve({ data: 'mocked data' }),
post: () => Promise.resolve({ success: true }),
delete: () => Promise.resolve({ deleted: true })
}
}));
// Mock utility modules
await mockModules.mock('@/utils/logger', () => ({
logger: {
info: () => {},
error: () => {},
warn: () => {}
}
}));
});
afterAll(() => {
mockModules.restoreAll();
});
it('should handle API calls with mocked services', async () => {
// Test code that uses the mocked modules
// The mocks are automatically available
expect(true).toBe(true); // Your actual test here
});
});
Cleanup Patterns
Ensure proper test isolation with cleanup utilities:
import {
setupTestCleanup,
withMockCleanup,
clearMockRegistry
} from '@rageltd/bun-test-utils';
// Pattern 1: Manual cleanup setup
describe('Manual Cleanup Example', () => {
setupTestCleanup(); // Sets up automatic mock.restore()
afterEach(() => {
clearMockRegistry(); // Clear custom registry
});
it('should clean up properly', () => {
// Test code here
});
});
// Pattern 2: Automatic cleanup wrapper
withMockCleanup(() => {
describe('Automatic Cleanup Example', () => {
// Cleanup is handled automatically
it('should handle cleanup behind the scenes', () => {
// Test code here - cleanup happens automatically
});
});
});
Module Restoration
Handle complex module mocking scenarios with proper restoration:
import { createModuleMocker, restoreModules } from '@rageltd/bun-test-utils';
// Pattern 1: Using createModuleMocker (recommended)
const mockModules = createModuleMocker();
describe('Module Mocker Pattern', () => {
beforeEach(async () => {
await mockModules.mock('@/config', () => ({
config: { apiUrl: 'http://test.api', timeout: 1000 }
}));
});
afterAll(() => {
mockModules.restoreAll(); // Restores all mocked modules
});
});
// Pattern 2: Manual restoration (advanced)
describe('Manual Restoration Pattern', () => {
// Store originals at the top of test file
const originals = {
config: import('@/config'),
utils: import('@/utils')
};
beforeEach(async () => {
// Apply mocks
// ... mocking code
});
afterAll(async () => {
// Restore manually
restoreModules({
'@/config': await originals.config,
'@/utils': await originals.utils
});
});
});
Testing Best Practices
✅ Do's
- Always call
setupTestCleanup()at the top of test files - Use
createModuleMocker()for consistent module mocking - Call
restoreAll()inafterAllhooks - Mock only what you need to isolate the unit under test
- Use descriptive test names that explain the expected behavior
- Group related tests with
describeblocks
❌ Don'ts
- Don't forget to restore mocks after tests
- Don't mock implementation details you don't need
- Don't share mock state between tests
- Don't rely on test execution order
- Don't mock everything - test real behavior when possible
Common Testing Patterns
Testing Error Conditions
describe('Error Handling', () => {
beforeEach(async () => {
// Mock a service that can throw errors
await mockModules.mock('@/services/api', () => ({
apiClient: {
get: () => Promise.reject(new Error('Network error')),
post: () => { throw new Error('Validation error'); }
}
}));
});
it('should handle network errors gracefully', async () => {
// Test error handling behavior
await expect(async () => {
// Code that uses the mocked failing service
}).toThrow('Network error');
});
it('should handle validation errors', () => {
expect(() => {
// Code that triggers validation error
}).toThrow('Validation error');
});
});
Testing Different Scenarios
describe('Feature Tests', () => {
it('should handle success case', async () => {
await mockModules.mock('@/services/user', () => ({
userService: {
getUser: () => Promise.resolve({ id: 1, name: 'John Doe' })
}
}));
// Test successful user retrieval
});
it('should handle user not found', async () => {
await mockModules.mock('@/services/user', () => ({
userService: {
getUser: () => Promise.resolve(null)
}
}));
// Test null user handling
});
it('should handle service unavailable', async () => {
await mockModules.mock('@/services/user', () => ({
userService: {
getUser: () => Promise.reject(new Error('Service unavailable'))
}
}));
// Test service error handling
});
});
Summary
Key takeaways for effective testing with @rageltd/bun-test-utils:
setupTestCleanup()
createModuleMocker() for consistent module mockingafterAll hooks