Testing Patterns with Vitest
This example demonstrates how to write declarative, behavior-focused tests for SyntropyLog applications using Vitest. The key insight is to avoid testing the framework itself and instead focus on testing your business logic.
๐ฏ What You'll Learnโ
- How to use
SyntropyLogMock
to avoid framework initialization issues - How to write tests that focus on behavior, not implementation
- How to use Vitest-specific features with SyntropyLog
- How to avoid testing external dependencies (Redis, brokers, etc.)
- How to create maintainable and readable tests
- How the mock simulates all framework functionality in memory
๐ Quick Startโ
- Install Dependencies
- Run Tests
- Watch Mode
- Coverage
npm install
npm test
npm run test:watch
npm run test:coverage
๐ Prerequisitesโ
- Node.js 18+ (using nvm:
source ~/.nvm/nvm.sh
) - npm or yarn
- Basic knowledge of Vitest and TypeScript
๐ง Setupโ
1. Install Dependenciesโ
npm install syntropylog vitest typescript
2. Configure Vitestโ
Create vitest.config.ts
:
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'node',
globals: true,
},
});
3. Configure TypeScriptโ
Create tsconfig.json
:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"types": ["vitest/globals", "node"]
},
"include": ["src/**/*", "tests/**/*"],
"exclude": ["node_modules", "dist"]
}
๐งช Testing Patternsโ
1. Basic Test Setup with SyntropyLogMockโ
import { describe, it, expect, beforeEach } from 'vitest';
import { UserService } from '../src/index';
const { createTestHelper } = require('syntropylog/testing');
// Create test helper - this creates a SyntropyLogMock that simulates the entire framework
// No real initialization/shutdown needed - everything is in memory
const testHelper = createTestHelper();
describe('UserService', () => {
let userService: UserService;
beforeEach(() => {
testHelper.beforeEach(); // Reset mocks and create fresh instances
userService = new UserService(testHelper.mockSyntropyLog); // Inject the mock
});
it('should create user successfully', async () => {
// Arrange
const userData = { name: 'John Doe', email: 'john@example.com' };
// Act
const result = await userService.createUser(userData);
// Assert - Test behavior, not implementation
expect(result).toHaveProperty('userId');
expect(result.name).toBe('John Doe');
});
});
2. Alternative: Service Helperโ
For even simpler setup, use the service helper:
it('should create user with service helper', async () => {
// Arrange
const userData = { name: 'John Doe', email: 'john@example.com' };
// Act - Create service with mock in one line
const { createServiceWithMock, createSyntropyLogMock } = require('syntropylog/testing');
const userService = createServiceWithMock(UserService, createSyntropyLogMock());
const result = await userService.createUser(userData);
// Assert
expect(result).toHaveProperty('userId');
});
๐ Understanding the SyntropyLogMockโ
The SyntropyLogMock
is a complete simulation of the SyntropyLog framework that runs entirely in memory. Here's what it provides:
What the Mock Simulatesโ
- Logger: Mock logger with all standard methods (info, warn, error, etc.)
- Context Manager: Mock context manager with correlation IDs
- HTTP Manager: Mock HTTP manager for testing HTTP operations
- Broker Manager: Mock broker manager for testing message brokers
- Serialization Manager: Mock serialization manager
Why Use the Mock?โ
- โ
No Initialization: No need to call
syntropyLog.init()
orsyntropyLog.shutdown()
- โ No External Dependencies: No Redis, brokers, or HTTP servers needed
- โ Fast Tests: Everything runs in memory
- โ Reliable: No network issues or state conflicts between tests
- โ Isolated: Each test gets a fresh mock instance
โก Vitest-Specific Featuresโ
1. Powerful Matchersโ
it('should use Vitest matchers', async () => {
const result = await userService.createUser(userData);
expect(result).toHaveProperty('name', 'John Doe');
expect(result.email).toMatch(/@/); // Regex matcher
expect(typeof result.name).toBe('string'); // Type checking
});
2. Async Testingโ
it('should handle async operations', async () => {
// Vitest handles async/await naturally
await expect(userService.getUserById('user-123'))
.resolves.not.toThrow();
});
3. Structure Validationโ
it('should validate object structure', async () => {
const result = await userService.createUser(userData);
// Test structure without depending on random values
expect(result).toHaveProperty('userId');
expect(result).toHaveProperty('name');
expect(result).toHaveProperty('email');
expect(typeof result.userId).toBe('string');
expect(result.userId.length).toBeGreaterThan(0);
expect(result.name).toBe('John Doe');
});
โ What's Being Testedโ
What We Testโ
- Business Logic: User creation, validation, retrieval
- Error Handling: Invalid inputs, edge cases
- Data Structures: Return values, object properties
- Async Operations: Promise resolution, error rejection
What We Don't Testโ
- Framework Features: Logging, context management, Redis operations
- External Dependencies: Database connections, HTTP calls
- Implementation Details: Internal method calls, private properties
๐ซ Common Pitfallsโ
1. Testing Framework Instead of Business Logicโ
โ Don't do this:
it('should log user creation', async () => {
// Testing if logging happened - framework responsibility
});
โ Do this instead:
it('should create user successfully', async () => {
const result = await userService.createUser(userData);
expect(result).toHaveProperty('userId');
});
2. Testing External Dependenciesโ
โ Don't do this:
it('should connect to Redis', async () => {
// Testing Redis connection - external dependency
});
โ Do this instead:
it('should handle user data correctly', async () => {
const result = await userService.createUser(userData);
expect(result).toHaveProperty('userId');
});
3. Over-Complicated Setupโ
โ Don't do this:
beforeEach(async () => {
// Complex setup with real framework initialization
await syntropyLog.initialize();
// ... more setup
});
โ Do this instead:
beforeEach(() => {
testHelper.beforeEach(); // Simple, clean setup
userService = new UserService(testHelper.mockSyntropyLog);
});
๐ฏ Next Stepsโ
- Run the tests:
npm test
- Explore the code: Look at
src/index.ts
to understand the service - Modify tests: Try adding your own test cases
- Check coverage:
npm run test:coverage
- Try other examples: Check examples 29 (Jest) and 30 (Redis context)
๐ค Contributingโ
When adding new tests:
- Follow the Arrange-Act-Assert pattern
- Use descriptive test names
- Focus on behavior, not implementation
- Keep tests simple and readable
- Use the provided helpers for consistent setup
๐ Related Examplesโ
The goal is to write tests that are readable, maintainable, and focused on business value. Let the framework handle the complexity, and focus your tests on what matters most: your business logic.
This example works seamlessly with the published syntropylog
package. When you install it via npm, the syntropylog/testing
module will be available automatically.
๐จ For AI Assistants (and other helpful bots)โ
If you're an AI assistant or helpful bot working on this codebase, here's what you need to know:
What This Example Doesโ
- Shows how to test SyntropyLog applications with Vitest
- Uses a complete mock that simulates the entire framework
- Focuses on testing business logic, not framework internals
Key Files to Understandโ
src/index.ts
- The UserService being testedtests/UserService.test.ts
- The actual teststests/test-helper.ts
- Helper functions for test setup
Common AI Mistakes to Avoidโ
- Don't try to initialize real SyntropyLog - Use the mock instead
- Don't test framework features - Test business logic only
- Don't create complex test setups - Use the provided helpers
- Don't test external dependencies - They're mocked for a reason
When Modifying This Exampleโ
- Keep tests simple and focused
- Use the existing patterns as templates
- Don't add unnecessary complexity
- Follow the Arrange-Act-Assert pattern
If Tests Failโ
- Check that you're using the mock correctly
- Ensure you're not trying to access real framework features
- Verify that the service is properly injected with the mock
- Look at the existing working tests as reference
Remember: This is about testing business logic, not the framework. Keep it simple!