Documentation Index
Fetch the complete documentation index at: https://www.orionjs.com/llms.txt
Use this file to discover all available pages before exploring further.
Orionjs uses Vitest for testing. This guide covers setup, conventions, and best practices.
Installation
bun add --dev vitest @vitest/coverage-v8
For MongoDB testing:
bun add --dev mongodb-memory-server
Configuration
Create a vitest.config.ts file in your project root:
import {defineConfig} from 'vitest/config'
import path from 'path'
export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['app/**/*.test.ts', 'app/**/*.spec.ts'],
setupFiles: ['./app/config/tests/setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
},
},
resolve: {
alias: {
app: path.resolve(__dirname, './app'),
},
},
})
Setup File
Create a ./app/config/tests/setup.ts file:
import {connections, createIndexesPromises, getMongoConnection} from '@orion-js/mongodb'
import {MongoMemoryServer} from 'mongodb-memory-server'
let mongod: MongoMemoryServer
beforeAll(async () => {
mongod = await MongoMemoryServer.create()
const uri = mongod.getUri()
process.env.MONGO_URL = uri
const connection = getMongoConnection({name: 'main'})
await connection.connectionPromise
})
afterAll(async () => {
await Promise.all(createIndexesPromises)
for (const connectionName in connections) {
const connection = connections[connectionName]
await connection.client.close()
}
if (mongod) {
await mongod.stop()
}
})
Test Conventions
Arrange-Act-Assert Pattern
Follow the AAA pattern for all tests:
describe('CreateUserService', () => {
it('should create a user with valid data', async () => {
// Arrange - Set up test data and mocks
const inputUserData = {
name: 'John Doe',
email: 'john@example.com'
}
const mockUsersRepo = {
createUser: vi.fn().mockResolvedValue({_id: 'usr-123', ...inputUserData})
}
// Act - Execute the code under test
const service = new CreateUserService()
const actualUser = await service.execute(inputUserData)
// Assert - Verify the results
const expectedUser = {_id: 'usr-123', name: 'John Doe', email: 'john@example.com'}
expect(actualUser).toEqual(expectedUser)
})
})
Variable Naming Convention
Use clear, consistent variable names:
inputX - Input data for the test
mockX - Mocked dependencies
actualX - The actual result from the code under test
expectedX - The expected result to compare against
it('should calculate total price', () => {
// Input
const inputItems = [{price: 10}, {price: 20}]
// Mock
const mockDiscountService = {getDiscount: () => 0.1}
// Act
const actualTotal = calculateTotal(inputItems, mockDiscountService)
// Assert
const expectedTotal = 27 // 30 - 10% discount
expect(actualTotal).toBe(expectedTotal)
})
Unit Tests
Write unit tests for each service function using test doubles to simulate dependencies:
import {mockService} from '@orion-js/services'
import {CreateCardService} from './CreateCardService'
import {CardsRepo} from '../repos/CardsRepo'
import {NotificationsRepo} from '../repos/NotificationsRepo'
describe('CreateCardService', () => {
it('should create a card and send notification', async () => {
// Arrange
const inputCardData = {name: 'Test Card', status: 'draft'}
const inputUserId = 'usr-123'
mockService(CardsRepo, {
createCard: vi.fn().mockResolvedValue({_id: 'crd-456', ...inputCardData})
})
mockService(NotificationsRepo, {
createNotification: vi.fn().mockResolvedValue({_id: 'ntf-789'})
})
// Act
const service = new CreateCardService()
const actualCard = await service.execute(inputCardData, inputUserId)
// Assert
expect(actualCard._id).toBe('crd-456')
expect(actualCard.name).toBe('Test Card')
})
})
Acceptance Tests
Write acceptance tests for each module following the Given-When-Then convention:
describe('User Registration Flow', () => {
it('should complete full registration process', async () => {
// Given - A new user with valid registration data
const inputRegistrationData = {
email: 'newuser@example.com',
password: 'SecurePass123',
name: 'New User'
}
// When - The user submits registration
const registrationService = new RegisterUserService()
const actualResult = await registrationService.execute(inputRegistrationData)
// Then - The user should be created and receive a welcome email
expect(actualResult.user).toBeDefined()
expect(actualResult.user.email).toBe('newuser@example.com')
expect(actualResult.emailSent).toBe(true)
})
})
Mocking Services
Use mockService from @orion-js/services to mock dependencies:
import {mockService} from '@orion-js/services'
import {UsersRepo} from '../repos/UsersRepo'
import {SecurityService} from './SecurityService'
describe('AuthenticateUserService', () => {
beforeEach(() => {
// Reset mocks before each test
vi.clearAllMocks()
})
it('should authenticate valid users', async () => {
// Mock the repository
mockService(UsersRepo, {
findByEmail: vi.fn().mockResolvedValue({
_id: 'usr-123',
email: 'test@example.com',
password: 'hashed_password',
salt: 'salt123',
failedLoginAttempts: 0,
roles: ['user']
}),
resetFailedLoginAttempts: vi.fn().mockResolvedValue(true)
})
// Mock services
mockService(SecurityService, {
verifyPassword: vi.fn().mockResolvedValue(true),
calculateEffectivePermissions: vi.fn().mockResolvedValue(['read:profile'])
})
const authService = new AuthenticateUserService()
const actualResult = await authService.execute('test@example.com', 'password')
expect(actualResult.token).toBeDefined()
})
})
Best Practices
-
Use test doubles for dependencies - Except for third-party dependencies that are not expensive to execute
-
Keep tests isolated - Each test should be independent and not rely on state from other tests
-
Test behavior, not implementation - Focus on what the code does, not how it does it
-
Use descriptive test names - Test names should describe the scenario being tested
-
Don’t mock data for dev or prod - Mocking data is only for tests
Running Tests
# Run all tests
bunx vitest
# Run tests in watch mode
bunx vitest --watch
# Run tests with coverage
bunx vitest --coverage
# Run specific test file
bunx vitest app/users/services/CreateUser/index.test.ts