Testing with Bun
Overview
All tests use Bun's built-in test runner. Never use Jest — imports and APIs differ.
Setup
typescript
import { describe, it, expect, mock, spyOn, beforeEach, afterEach } from 'bun:test'File Naming
- Unit tests:
[ClassName].test.ts - Integration tests:
[ClassName].integration.test.ts
Tests live in packages/<context>/test/ mirroring the source structure.
AAA Pattern (Arrange, Act, Assert)
Always follow this structure:
typescript
it('should save the coin blockchain', async () => {
// Arrange
const params = { coinId: 'BTC', blockchain: 'BTC' }
const repository = { save: mock(() => Promise.resolve()) } as unknown as CoinBlockchainRepository
// Act
const service = new CoinBlockchainCreator(repository)
await service.run(params)
// Assert
expect(repository.save).toHaveBeenCalled()
})Mocking
Use mock() from bun:test. Cast with as unknown as InterfaceType:
typescript
import { mock } from 'bun:test'
import type { CexSpotAccountPort } from '../../../domain/ports/CexSpotAccountPort'
const cexAdapter: CexSpotAccountPort = {
coinBlockchainList: mock(() => Promise.resolve([])),
depositAddress: mock(() => Promise.resolve(null))
} as unknown as CexSpotAccountPortOverride mock return values per test:
typescript
it('should handle empty list', async () => {
cexAdapter.coinBlockchainList = mock(() => Promise.resolve([]))
await service.run({ exchange: 'binance' })
expect(repository.save).not.toHaveBeenCalled()
})Object Mother Pattern
Test builders live in packages/<context>/test/mothers/[Entity]Mother.ts. They provide sensible defaults with optional overrides:
typescript
// packages/cex-wallets/test/mothers/CexAccountMother.ts
import { CexAccount } from '../../domain/entities/CexAccount'
import { CEX_BINANCE_MAIN_ACCOUNT } from '../test-constants'
export class CexAccountMother {
static create(
params: {
id?: string
cex?: string
active?: boolean
} = {}
): CexAccount {
return CexAccount.fromDto({ ...CEX_BINANCE_MAIN_ACCOUNT, ...params })
}
}Usage:
typescript
const account = CexAccountMother.create()
const inactiveAccount = CexAccountMother.create({ active: false })
const kuCoinAccount = CexAccountMother.create({ cex: 'kucoin' })Test Constants
test-constants.ts holds reusable test data (sanitized, no real credentials):
typescript
// packages/cex-wallets/test/test-constants.ts
export const CEX_BINANCE_MAIN_ACCOUNT = {
id: 'test-account-id',
cex: 'binance',
accountType: 'MAIN',
active: true,
encryptedApiKey: 'encrypted-test-key',
encryptedApiSecret: 'encrypted-test-secret'
}Complete Service Test Example
typescript
import { describe, it, expect, mock, beforeEach } from 'bun:test'
import { CoinBlockchainCreator } from '../../../application/services/CoinBlockchainCreator'
import type { CoinBlockchainRepository } from '../../../domain/repositories/CoinBlockchainRepository'
import type { EventPublisher } from '@sws/common/domain'
describe('CoinBlockchainCreator', () => {
let repository: CoinBlockchainRepository
let eventPublisher: EventPublisher
let service: CoinBlockchainCreator
beforeEach(() => {
repository = {
save: mock(() => Promise.resolve()),
findById: mock(() => Promise.resolve(null))
} as unknown as CoinBlockchainRepository
eventPublisher = {
publish: mock(() => Promise.resolve())
} as unknown as EventPublisher
service = new CoinBlockchainCreator(repository, eventPublisher)
})
it('should save a coin blockchain', async () => {
await service.run({ coinId: 'BTC', blockchain: 'BTC' })
expect(repository.save).toHaveBeenCalled()
})
it('should publish a domain event', async () => {
await service.run({ coinId: 'BTC', blockchain: 'BTC' })
expect(eventPublisher.publish).toHaveBeenCalled()
})
})Factory Test Overrides
Use the factory with TestDependencyOverrides to inject mocks:
typescript
import type { TestDependencyOverrides } from '@sws/common/test'
import { CexAccountFactory } from '../../infrastructure/factories/CexAccountFactory'
const mockRepository = {
findById: mock(() => Promise.resolve(null)),
save: mock(() => Promise.resolve())
} as unknown as CexAccountRepository
const overrides: TestDependencyOverrides = { cexAccountRepository: mockRepository }
const service = CexAccountFactory.coinBlockchainCreator(overrides)Running Tests
bash
bun test # All tests
bun test packages/cex-wallets # Specific package
bun test CoinBlockchainCreator # Match by name
bun test --watch # Watch mode
TEST_LOGGER=1 bun test # With logs enabled