Testing
One of Vla’s core benefits is making your code easy to test. With dependency injection, you can mock dependencies without complex module mocking systems.
Traditional testing often requires mocking entire modules:
// ❌ Without Vla: Complex module mockingimport { vi } from 'vitest'
vi.mock('./database', () => ({ db: { users: { find: vi.fn() } }}))
// Mock has to be defined before importsimport { getUserById } from './users'With Vla, you inject mocks directly:
// ✅ With Vla: Direct dependency mockingconst kernel = new Kernel()kernel.bind(UserRepo, MockUserRepo)
const service = kernel.create(UserService)Testing Actions
Section titled “Testing Actions”class GetUser extends Vla.Action { service = this.inject(UserService)
async handle(id: string) { return this.service.getUser(id) }}
test('GetUser action calls service', async () => { const kernel = new Kernel()
// Mock the service kernel.bind( UserService, class MockUserService { getUser = vi.fn().mockResolvedValue({ id: '1', name: 'Test' }) } )
const result = await GetUser.withKernel(kernel).invoke('1')
expect(result).toEqual({ id: '1', name: 'Test' })})Testing Services
Section titled “Testing Services”import { test, expect, vi } from 'vitest'import { Kernel } from 'vla'
class UserService extends Vla.Service { repo = this.inject(UserRepo)
async getUser(id: string) { return this.repo.findById(id) }}
test('getUser returns user from repo', async () => { const kernel = new Kernel()
// Mock the repository kernel.bind( UserRepo, class MockUserRepo { findById = vi.fn().mockResolvedValue({ id: '1', name: 'Test User' }) } )
const service = kernel.create(UserService) const user = await service.getUser('1')
expect(user).toEqual({ id: '1', name: 'Test User' })})Testing with Context
Section titled “Testing with Context”Mock context values in tests:
const AppContext = Vla.createContext<{ userId: string | null}>()
class SessionService extends Vla.Service { ctx = this.inject(AppContext)
async currentUser() { return this.ctx.userId }}
test('returns current user from context', async () => { const kernel = new Kernel() kernel.context(AppContext, { userId: 'test-user' })
const service = kernel.create(SessionService) const userId = await service.currentUser()
expect(userId).toBe('test-user')})
test('handles unauthenticated users', async () => { const kernel = new Kernel() kernel.context(AppContext, { userId: null })
const service = kernel.create(SessionService) const userId = await service.currentUser()
expect(userId).toBeNull()})Mocking Strategies
Section titled “Mocking Strategies”Mock with Class
Section titled “Mock with Class”Create a mock class that implements the same interface:
class MockUserRepo { findById = vi.fn().mockResolvedValue({ id: '1', name: 'Test' }) findAll = vi.fn().mockResolvedValue([]) create = vi.fn()}
test('example', async () => { const kernel = new Kernel() kernel.bind(UserRepo, MockUserRepo)
const service = kernel.create(UserService) // ...})Mock with Object
Section titled “Mock with Object”For simpler cases, use plain objects:
test('example', async () => { const kernel = new Kernel()
const mockRepo = { findById: vi.fn().mockResolvedValue({ id: '1', name: 'Test' }) }
kernel.bindValue(UserRepo, mockRepo)
const service = kernel.create(UserService) // ...})Integration Testing
Section titled “Integration Testing”Test multiple layers together:
test('full integration test', async () => { const kernel = new Kernel().scoped()
// Only mock external dependencies kernel.bind( Database, class MockDatabase { users = { find: vi.fn().mockResolvedValue({ id: '1', name: 'Real User' }) } } )
// Real service and repo implementations const action = GetUser.withKernel(kernel) const result = await action.invoke('1')
expect(result.name).toBe('Real User')})Test Fixtures
Section titled “Test Fixtures”Create reusable test fixtures:
export function createTestKernel() { const kernel = new Kernel()
kernel.bind( Database, class MockDatabase { users = { find: vi.fn(), create: vi.fn(), update: vi.fn() } } )
kernel.context(AppContext, { userId: 'test-user', headers: new Headers() })
return kernel}
// In your teststest('example', async () => { const kernel = createTestKernel() const service = kernel.create(UserService) // ...})Testing Best Practices
Section titled “Testing Best Practices”Use Scoped Kernels
Section titled “Use Scoped Kernels”// ✅ Good: Fresh kernel per testtest('example', async () => { const kernel = new Kernel() // ...})
// ❌ Bad: Shared kernel across testsconst globalKernel = new Kernel()
test('test 1', () => { // State might leak between tests})Mock External Dependencies
Section titled “Mock External Dependencies”// ✅ Good: Mock external serviceskernel.bind(Database, MockDatabase)kernel.bind(EmailService, MockEmailService)
// ✅ Good: Use real business logicconst service = kernel.create(UserService) // Real implementationTest Business Logic, Not Framework
Section titled “Test Business Logic, Not Framework”// ✅ Good: Test what the service doestest('creates user with validated email', async () => { const service = kernel.create(UserService)})
// ❌ Bad: Testing Vla's DI systemtest('injects UserRepo', () => { const service = kernel.create(UserService) expect(service.repo).toBeInstanceOf(UserRepo)})