Testing API
Testing utilities reference
Complete reference for @robojs/mock/testing utilities.
Session Management
createTestSession
Create an isolated test session:
import { createTestSession } from '@robojs/mock/testing'
const session = await createTestSession(__filename, {
name: 'my-test',
config: {
botUser: { username: 'TestBot' },
guilds: [{ name: 'Test Server' }]
}
})import { createTestSession } from '@robojs/mock/testing'
const session = await createTestSession(__filename, {
name: 'my-test',
config: {
botUser: { username: 'TestBot' },
guilds: [{ name: 'Test Server' }]
}
})Parameters:
| Parameter | Type | Description |
|---|---|---|
testFilePath | string | Path to test file (use __filename) |
options.name | string | Session display name |
options.ttl | number | Session TTL in ms (default: 3600000 = 1 hour) |
options.config | object | Session configuration |
Session Config Options:
| Option | Type | Default | Description |
|---|---|---|---|
botUser | object | auto-generated | Bot user identity (username, id) |
guilds | array | one default guild | Pre-seeded guilds with channels |
users | array | - | Pre-seeded users |
applicationId | string | auto-generated | Application ID for the bot |
commands | array | - | Commands to seed in session |
maxActions | number | 10000 | Max recorded actions before LRU eviction |
maxLogs | number | 10000 | Max recorded logs before LRU eviction |
enforceIntents | boolean | false | Filter events by declared intents |
approvedPrivilegedIntents | bigint | - | Privileged intents bitmask |
permissionEnforcement | string | 'none' | Permission enforcement level |
Returns: Promise<TestSession>
TestSession
interface TestSession {
id: string // Session ID
token: string // Bot token (mock:sess_xxx)
name?: string // Display name
botUser: {
id: string
username: string
}
guilds: Array<{
id: string
name: string
}>
channels: Array<{
id: string
name: string
guildId?: string
type: number
}>
guildId: string // First guild ID
testFilePath?: string // Associated test file
destroy(): Promise<void> // Cleanup
}// TestSession shape:
// {
// id: string, // Session ID
// token: string, // Bot token (mock:sess_xxx)
// name: string, // Display name
// botUser: { id, username },
// guilds: [{ id, name }],
// channels: [{ id, name, guildId, type }],
// guildId: string, // First guild ID
// testFilePath: string, // Associated test file
// destroy(): Promise<void> // Cleanup
// }deleteSession
Delete a session:
import { deleteSession } from '@robojs/mock/testing'
await deleteSession(session.id)import { deleteSession } from '@robojs/mock/testing'
await deleteSession(session.id)resetSession
Reset session to initial state:
import { resetSession } from '@robojs/mock/testing'
await resetSession(session.id)import { resetSession } from '@robojs/mock/testing'
await resetSession(session.id)Clears messages, actions, and state changes while preserving configuration.
Event Dispatch
dispatchEvent
Inject any Discord Gateway event:
import { dispatchEvent } from '@robojs/mock/testing'
await dispatchEvent(sessionId, 'MESSAGE_CREATE', {
id: '123456789',
content: 'Hello',
channel_id: channelId,
author: {
id: userId,
username: 'TestUser'
}
})import { dispatchEvent } from '@robojs/mock/testing'
await dispatchEvent(sessionId, 'MESSAGE_CREATE', {
id: '123456789',
content: 'Hello',
channel_id: channelId,
author: {
id: userId,
username: 'TestUser'
}
})Parameters:
| Parameter | Type | Description |
|---|---|---|
sessionId | string | Target session |
eventName | string | Discord event type |
data | Record<string, unknown> | Event payload |
Returns: Promise<{ success: boolean; dispatched: number }>
dispatchInteraction
Trigger interactions (commands, buttons, etc.):
import { dispatchInteraction } from '@robojs/mock/testing'
// Slash command
await dispatchInteraction(sessionId, {
type: 2,
data: {
name: 'ping',
type: 1
},
channel_id: channelId,
guild_id: guildId
})
// Button
await dispatchInteraction(sessionId, {
type: 3,
data: {
component_type: 2,
custom_id: 'my-button'
},
channel_id: channelId,
message: { id: messageId }
})import { dispatchInteraction } from '@robojs/mock/testing'
// Slash command
await dispatchInteraction(sessionId, {
type: 2,
data: {
name: 'ping',
type: 1
},
channel_id: channelId,
guild_id: guildId
})
// Button
await dispatchInteraction(sessionId, {
type: 3,
data: {
component_type: 2,
custom_id: 'my-button'
},
channel_id: channelId,
message: { id: messageId }
})Interaction Types:
| Type | Description |
|---|---|
| 2 | Application Command |
| 3 | Message Component |
| 4 | Autocomplete |
| 5 | Modal Submit |
Returns: Promise<{ success: boolean; interaction_id: string }>
Assertions
expectAction
Assert an action occurred:
import { expectAction } from '@robojs/mock/testing'
await expectAction(sessionId, {
description: 'Bot should respond with pong',
type: 'interaction_response',
expected: {
response_data: {
content: expect.stringContaining('Pong')
}
},
timeout: 5000
})import { expectAction } from '@robojs/mock/testing'
await expectAction(sessionId, {
description: 'Bot should respond with pong',
type: 'interaction_response',
expected: {
response_data: {
content: expect.stringContaining('Pong')
}
},
timeout: 5000
})Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
description | string | - | Human-readable description |
type | ActionType | - | Action type to match |
expected | unknown | - | Expected data shape |
timeout | number | 5000 | Wait timeout in ms |
Returns: Promise<RecordedAction> - The matched action (useful for further assertions)
Action Types:
| Type | Description |
|---|---|
interaction_response | Interaction reply |
message_sent | Message created via REST |
message_edited | Message edited via REST |
message_deleted | Message deleted via REST |
reaction_added | Reaction added to message |
These are just the most common action types. See the Complete Action Type Reference below for the full list of 80+ types covering messages, interactions, channels, guilds, voice, and more.
expectNoAction
Assert an action did not occur:
import { expectNoAction } from '@robojs/mock/testing'
await expectNoAction(sessionId, {
description: 'Bot should not respond',
type: 'message_sent',
waitMs: 2000
})import { expectNoAction } from '@robojs/mock/testing'
await expectNoAction(sessionId, {
description: 'Bot should not respond',
type: 'message_sent',
waitMs: 2000
})Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
description | string | - | Human-readable description |
type | string | - | Action type to check |
waitMs | number | 500 | Time to wait before asserting (ms) |
Note: expectNoAction uses waitMs with a 500ms default, different from expectAction which uses timeout with a 5000ms default.
waitForAction
Wait for actions and return them:
import { waitForAction } from '@robojs/mock/testing'
const actions = await waitForAction(sessionId, {
type: 'interaction_response',
timeout: 5000
})
// Inspect result -- use a typed cast to avoid `as any`
const data = actions[0].data as { response_data?: { content?: string } }
expect(data.response_data?.content).toBe('Success')
// Shorthand: pass a string instead of options object
const actions = await waitForAction(sessionId, 'message_sent')import { waitForAction } from '@robojs/mock/testing'
const actions = await waitForAction(sessionId, {
type: 'interaction_response',
timeout: 5000
})
// Inspect result
expect(actions[0].data.response_data?.content).toBe('Success')
// Shorthand: pass a string instead of options object
const actions = await waitForAction(sessionId, 'message_sent')Options:
// Full options object
interface WaitForActionOptions {
type: string // Action type to wait for
timeout?: number // Default: 5000ms
filter?: (action: RecordedAction) => boolean // Additional filtering
}
// Or pass a string (treated as { type: string })
waitForAction(sessionId, 'message_sent')// Full options object: { type, timeout, filter }
// Or pass a string (treated as { type: string })
waitForAction(sessionId, 'message_sent')Returns: Promise<RecordedAction[]>
Avoiding as any casts: Instead of (actions[0].data as any).content, use a typed cast like actions[0].data as { response_data?: { content?: string } }. For common cases, prefer the more specific helpers waitForMessage or waitForInteractionResponse which return targeted results.
waitForAnyAction
Wait for any action matching a filter:
import { waitForAnyAction } from '@robojs/mock/testing'
const action = await waitForAnyAction(sessionId, (action) => {
return action.type === 'message_sent' &&
(action.data as any).content?.includes('welcome')
})
// With custom timeout
const action = await waitForAnyAction(
sessionId,
(action) => action.type === 'interaction_response',
10000 // 10 second timeout
)import { waitForAnyAction } from '@robojs/mock/testing'
const action = await waitForAnyAction(sessionId, (action) => {
return action.type === 'message_sent' &&
action.data.content?.includes('welcome')
})
// With custom timeout
const action = await waitForAnyAction(
sessionId,
(action) => action.type === 'interaction_response',
10000 // 10 second timeout
)Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
sessionId | string | - | Session ID |
filter | function | - | Filter (action: RecordedAction) => boolean |
timeout | number | 5000 | Wait timeout in ms |
Returns: Promise<RecordedAction>
waitForMessage
Wait for a message:
import { waitForMessage } from '@robojs/mock/testing'
const action = await waitForMessage(sessionId, {
channelId: channelId,
content: /hello/i, // Optional: filter by content (string or RegExp)
timeout: 5000
})
// All options are optional
const action = await waitForMessage(sessionId)import { waitForMessage } from '@robojs/mock/testing'
const action = await waitForMessage(sessionId, {
channelId: channelId,
content: /hello/i, // Optional: filter by content (string or RegExp)
timeout: 5000
})
// All options are optional
const action = await waitForMessage(sessionId)Options:
| Option | Type | Description |
|---|---|---|
channelId | string | Filter by channel (optional) |
content | string | RegExp | Filter by message content (optional) |
timeout | number | Wait timeout in ms (default: 5000) |
Returns: Promise<RecordedAction>
waitForInteractionResponse
Wait for an interaction response:
import { waitForInteractionResponse } from '@robojs/mock/testing'
const action = await waitForInteractionResponse(sessionId, {
type: 4, // Optional: filter by response type
timeout: 5000
})import { waitForInteractionResponse } from '@robojs/mock/testing'
const action = await waitForInteractionResponse(sessionId, {
type: 4, // Optional: filter by response type
timeout: 5000
})Options:
| Option | Type | Description |
|---|---|---|
type | number | Filter by interaction response type (4 = Reply, 5 = Deferred) |
timeout | number | Wait timeout in ms (default: 5000) |
Returns: Promise<RecordedAction>
State Inspection
getSessionState
Get current session state:
import { getSessionState } from '@robojs/mock/testing'
const state = await getSessionState(sessionId)
// Inspect state
console.log(state.botUser)
console.log(state.guilds)
console.log(state.channels)import { getSessionState } from '@robojs/mock/testing'
const state = await getSessionState(sessionId)
// Inspect state
console.log(state.botUser)
console.log(state.guilds)
console.log(state.channels)getSessionActions
Get recorded actions:
import { getSessionActions } from '@robojs/mock/testing'
// All actions
const result = await getSessionActions(sessionId)
const actions = result.actions
// Filtered by type with limit
const result = await getSessionActions(sessionId, {
type: 'message_sent',
limit: 100,
since: Date.now() - 60000 // Last minute
})import { getSessionActions } from '@robojs/mock/testing'
// All actions
const result = await getSessionActions(sessionId)
const actions = result.actions
// Filtered by type with limit
const result = await getSessionActions(sessionId, {
type: 'message_sent',
limit: 100,
since: Date.now() - 60000 // Last minute
})Options:
| Option | Type | Description |
|---|---|---|
type | string | Filter by action type |
limit | number | Maximum actions to return |
since | number | Only actions after this timestamp (ms) |
Returns: Promise<{ actions: RecordedAction[] }>
getChannelMessages
Get messages from a channel:
import { getChannelMessages } from '@robojs/mock/testing'
// Get last 50 messages (default)
const messages = await getChannelMessages(sessionId, channelId)
// Get last 100 messages
const messages = await getChannelMessages(sessionId, channelId, 100)import { getChannelMessages } from '@robojs/mock/testing'
// Get last 50 messages (default)
const messages = await getChannelMessages(sessionId, channelId)
// Get last 100 messages
const messages = await getChannelMessages(sessionId, channelId, 100)Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
sessionId | string | - | Session ID |
channelId | string | - | Channel ID |
limit | number | 50 | Maximum messages to return |
Returns: Promise<MockMessage[]>
getHistoricalActions
Get all actions including pre-test:
import { getHistoricalActions } from '@robojs/mock/testing'
// Get all historical actions
const allActions = await getHistoricalActions(sessionId)
// Filter by type
const interactions = await getHistoricalActions(sessionId, {
type: 'interaction_response'
})
// Custom filter
const filtered = await getHistoricalActions(sessionId, {
filter: (action) => (action.data as any)?.content?.includes('hello')
})import { getHistoricalActions } from '@robojs/mock/testing'
// Get all historical actions
const allActions = await getHistoricalActions(sessionId)
// Filter by type
const interactions = await getHistoricalActions(sessionId, {
type: 'interaction_response'
})
// Custom filter
const filtered = await getHistoricalActions(sessionId, {
filter: (action) => action.data?.content?.includes('hello')
})Options:
| Option | Type | Description |
|---|---|---|
type | string | Filter by action type |
filter | function | Custom filter (action: RecordedAction) => boolean |
Returns: Promise<RecordedAction[]>
Bot Lifecycle
startMockRobo
Start a bot connected to mock server:
import { startMockRobo } from '@robojs/mock/testing'
const handle = await startMockRobo({
name: 'test-bot',
testFilePath: __filename,
hmr: true,
logLevel: 'debug'
})
// ... run tests ...
await handle.stop()import { startMockRobo } from '@robojs/mock/testing'
const handle = await startMockRobo({
name: 'test-bot',
testFilePath: __filename,
hmr: true,
logLevel: 'debug'
})
// ... run tests ...
await handle.stop()Options:
interface StartMockRoboOptions {
name?: string // Bot name
port?: number // Server port
timeout?: number // Connection timeout (default: 30000ms)
verbose?: boolean // Verbose output
testFilePath?: string // Associated test file
logFile?: boolean | string // Log to file
logLevel?: 'trace' | 'debug' | 'info' | 'warn' | 'error'
hmr?: boolean // Hot module replacement
}// StartMockRoboOptions shape:
// {
// name: string, // Bot name
// port: number, // Server port
// timeout: number, // Connection timeout (default: 30000ms)
// verbose: boolean, // Verbose output
// testFilePath: string, // Associated test file
// logFile: boolean | string, // Log to file
// logLevel: string, // 'trace' | 'debug' | 'info' | 'warn' | 'error'
// hmr: boolean // Hot module replacement
// }| Option | Type | Default | Description |
|---|---|---|---|
name | string | - | Bot name |
port | number | - | Server port |
timeout | number | 30000 | Connection timeout (ms). HMR mode uses 60000ms. |
verbose | boolean | false | Verbose output |
testFilePath | string | - | Associated test file |
logFile | boolean | string | - | Log to file (true = auto, string = path) |
logLevel | string | debug | Log verbosity level |
hmr | boolean | false | Enable hot module replacement |
Returns: Promise<MockRoboHandle>
MockRoboHandle
interface MockRoboHandle {
client: unknown // Discord.js client (null when hmr: true)
sessionId: string // Session ID
token: string // Bot token
botUser: { // Bot user info
id: string
username: string
}
guilds: Array<{ // Guild list
id: string
name: string
}>
channels: Array<{ // Channel list
id: string
name: string
guildId?: string
type: number
}>
guildId: string // First guild ID (convenience)
stop(): Promise<void> // Cleanup
// HMR-only properties (available when hmr: true)
process?: ChildProcess // Child process handle
getHmrCount?: () => number // Get current HMR reload count
getRestartCount?: () => number // Get current restart count
waitForHmrReload?: (timeout?: number, fromCount?: number) => Promise<void>
waitForFullRestart?: (timeout?: number, fromCount?: number) => Promise<void>
}// MockRoboHandle shape:
// {
// client, // Discord.js client (null when hmr: true)
// sessionId, // Session ID
// token, // Bot token
// botUser: { id, username },
// guilds: [{ id, name }],
// channels: [{ id, name, guildId, type }],
// guildId, // First guild ID (convenience)
// stop(), // Cleanup
// // HMR-only properties:
// process, // Child process handle
// getHmrCount(), // Get current HMR reload count
// getRestartCount(), // Get current restart count
// waitForHmrReload(timeout, fromCount),
// waitForFullRestart(timeout, fromCount)
// }waitForMockServer
Wait for mock server to be ready:
import { waitForMockServer } from '@robojs/mock/testing'
await waitForMockServer({
timeout: 30000
})
// With custom URL and poll interval
await waitForMockServer({
url: 'http://localhost:4000/api/control',
timeout: 60000,
interval: 1000 // Check every 1 second
})import { waitForMockServer } from '@robojs/mock/testing'
await waitForMockServer({
timeout: 30000
})
// With custom URL and poll interval
await waitForMockServer({
url: 'http://localhost:4000/api/control',
timeout: 60000,
interval: 1000 // Check every 1 second
})Options:
| Option | Type | Default | Description |
|---|---|---|---|
url | string | auto-detected | Control API URL to check |
timeout | number | 30000 | Maximum wait time in ms |
interval | number | 500 | Polling interval in ms |
User Testing
TestUsers
Manage test users:
import { TestUsers } from '@robojs/mock/testing'
const users = new TestUsers(session)
// Create users
const alice = users.create('Alice')
const bob = users.create('Bob', { status: 'online' })
const bot = users.create('HelperBot', { bot: true, avatar: 'abc123' })
// Create multiple users at once
const [charlie, diana, eve] = users.createMany(['Charlie', 'Diana', 'Eve'])
// Switch active user
users.switchTo(alice)
// Get users
const current = users.current()
const byName = users.byName('Alice')
const byId = users.byId('123456789') // Find by user ID
const allHumans = users.allHumans()
const allBots = users.allBots()
// Execute as user
await users.as(bob, async () => {
// Actions here are from Bob's perspective
})import { TestUsers } from '@robojs/mock/testing'
const users = new TestUsers(session)
// Create users
const alice = users.create('Alice')
const bob = users.create('Bob', { status: 'online' })
const bot = users.create('HelperBot', { bot: true, avatar: 'abc123' })
// Create multiple users at once
const [charlie, diana, eve] = users.createMany(['Charlie', 'Diana', 'Eve'])
// Switch active user
users.switchTo(alice)
// Get users
const current = users.current()
const byName = users.byName('Alice')
const byId = users.byId('123456789') // Find by user ID
const allHumans = users.allHumans()
const allBots = users.allBots()
// Execute as user
await users.as(bob, async () => {
// Actions here are from Bob's perspective
})create() Options:
| Option | Type | Description |
|---|---|---|
bot | boolean | Create as bot user |
avatar | string | null | Avatar hash |
status | string | User status: 'online' | 'offline' | 'idle' | 'dnd' |
createTestUtils
Create session-bound helpers. Accepts either a Session instance or a sessionId string:
import { startMockRobo, createTestUtils } from '@robojs/mock/testing'
const bot = await startMockRobo({ name: 'test' })
const { users, interactions } = createTestUtils(bot.sessionId)
// Send messages
await interactions.sendMessage(users.current(), channelId, 'Hello')
// Invoke commands
await interactions.invokeCommand(users.current(), 'ping')
// Invoke commands with options
await interactions.invokeCommand(users.current(), 'echo', { message: 'Hello' })
// Click buttons
await interactions.clickButton(users.current(), messageId, 'button-custom-id')
// Simulate a multi-user conversation (user is a username string)
await interactions.conversation(channelId, [
{ user: 'Alice', content: 'Hello everyone!' },
{ user: 'Bob', content: 'Hi Alice!' },
{ user: 'Alice', content: 'How are you?' }
])import { startMockRobo, createTestUtils } from '@robojs/mock/testing'
const bot = await startMockRobo({ name: 'test' })
const { users, interactions } = createTestUtils(bot.sessionId)
// Send messages
await interactions.sendMessage(users.current(), channelId, 'Hello')
// Invoke commands
await interactions.invokeCommand(users.current(), 'ping')
// Invoke commands with options
await interactions.invokeCommand(users.current(), 'echo', { message: 'Hello' })
// Click buttons
await interactions.clickButton(users.current(), messageId, 'button-custom-id')
// Simulate a multi-user conversation (user is a username string)
await interactions.conversation(channelId, [
{ user: 'Alice', content: 'Hello everyone!' },
{ user: 'Bob', content: 'Hi Alice!' },
{ user: 'Alice', content: 'How are you?' }
])Parameters:
| Parameter | Type | Description |
|---|---|---|
sessionOrId | Session | string | Session instance or session ID string |
When given a string, the session is resolved from the in-process session manager. This requires the mock server to be running in the same process (standard mode, not HMR mode).
Returns: TestUtils
Utilities
sleep
Wait for specified duration:
import { sleep } from '@robojs/mock/testing'
await sleep(1000) // Wait 1 secondimport { sleep } from '@robojs/mock/testing'
await sleep(1000) // Wait 1 secondgenerateSnowflake
Generate a Discord snowflake ID:
import { generateSnowflake } from '@robojs/mock/testing'
const id = generateSnowflake() // '1234567890123456789'import { generateSnowflake } from '@robojs/mock/testing'
const id = generateSnowflake() // '1234567890123456789'deepEquals
Deep equality comparison:
import { deepEquals } from '@robojs/mock/testing'
const equal = deepEquals(actual, expected)import { deepEquals } from '@robojs/mock/testing'
const equal = deepEquals(actual, expected)generateDiff
Generate diff between values:
import { generateDiff } from '@robojs/mock/testing'
const diff = generateDiff(expected, actual)import { generateDiff } from '@robojs/mock/testing'
const diff = generateDiff(expected, actual)clearSessionActions
Clear all recorded actions for a session:
import { clearSessionActions } from '@robojs/mock/testing'
await clearSessionActions(sessionId)import { clearSessionActions } from '@robojs/mock/testing'
await clearSessionActions(sessionId)Useful for resetting action history between test phases without resetting the full session state.
mockRestAPI
Make direct REST API calls to the mock server:
import { mockRestAPI } from '@robojs/mock/testing'
// Get user's guilds
const guilds = await mockRestAPI(token, '/users/@me/guilds')
// Create a message directly
const message = await mockRestAPI(token, `/channels/${channelId}/messages`, {
method: 'POST',
body: { content: 'Hello from REST!' }
})import { mockRestAPI } from '@robojs/mock/testing'
// Get user's guilds
const guilds = await mockRestAPI(token, '/users/@me/guilds')
// Create a message directly
const message = await mockRestAPI(token, `/channels/${channelId}/messages`, {
method: 'POST',
body: { content: 'Hello from REST!' }
})Useful for testing REST API interactions directly without going through Discord.js.
controlAPI
Make direct calls to the mock server's control API:
import { controlAPI } from '@robojs/mock/testing'
// Get session info
const session = await controlAPI(`/sessions/${sessionId}`)
// Create a new session
const newSession = await controlAPI('/sessions', {
method: 'POST',
body: { name: 'test' }
})
// Delete a session
await controlAPI(`/sessions/${sessionId}`, { method: 'DELETE' })import { controlAPI } from '@robojs/mock/testing'
// Get session info
const session = await controlAPI(`/sessions/${sessionId}`)
// Create a new session
const newSession = await controlAPI('/sessions', {
method: 'POST',
body: { name: 'test' }
})
// Delete a session
await controlAPI(`/sessions/${sessionId}`, { method: 'DELETE' })Parameters:
| Parameter | Type | Description |
|---|---|---|
endpoint | string | Control API endpoint (e.g., /sessions) |
options.method | string | HTTP method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' |
options.body | unknown | Request body (automatically serialized to JSON) |
Returns: Promise<T> (generic, based on endpoint)
Useful for advanced scenarios where you need direct control over the mock server.
Configuration
configureMock
Configure the mock server connection:
import { configureMock } from '@robojs/mock/testing'
configureMock({
baseUrl: 'http://localhost:3000/mock',
defaultTimeout: 10000
})import { configureMock } from '@robojs/mock/testing'
configureMock({
baseUrl: 'http://localhost:3000/mock',
defaultTimeout: 10000
})getMockConfig
Get current mock configuration:
import { getMockConfig } from '@robojs/mock/testing'
const config = getMockConfig()
// { baseUrl, controlUrl, restUrl, gatewayUrl, defaultTimeout }import { getMockConfig } from '@robojs/mock/testing'
const config = getMockConfig()
// { baseUrl, controlUrl, restUrl, gatewayUrl, defaultTimeout }Returns:
interface MockConfig {
baseUrl: string // Base URL for the mock server
controlUrl: string // Control API URL
restUrl: string // REST API URL
gatewayUrl: string // Gateway WebSocket URL
defaultTimeout: number // Default timeout for operations (ms)
}// MockConfig shape:
// {
// baseUrl: string, // Base URL for the mock server
// controlUrl: string, // Control API URL
// restUrl: string, // REST API URL
// gatewayUrl: string, // Gateway WebSocket URL
// defaultTimeout: number // Default timeout for operations (ms)
// }resetMockConfig
Reset configuration to defaults:
import { resetMockConfig } from '@robojs/mock/testing'
resetMockConfig()import { resetMockConfig } from '@robojs/mock/testing'
resetMockConfig()RecordedAction
Structure of recorded actions:
interface RecordedAction {
id: string // Unique ID
type: string // Action type (e.g., 'interaction_response')
data: unknown // Action payload
timestamp: number // Unix timestamp (ms)
sequence?: number // Optional sequence number
}// RecordedAction shape:
// {
// id: string, // Unique ID
// type: string, // Action type (e.g., 'interaction_response')
// data: any, // Action payload
// timestamp: number, // Unix timestamp (ms)
// sequence: number // Optional sequence number
// }At runtime, actions returned from the control API may include additional fields beyond the typed interface, such as endpoint, method, interactionId, responseType, triggeredBy, and metadata. These are useful for debugging but not part of the exported TypeScript type.
Action Types
Action types follow patterns based on their source:
| Pattern | Example | Description |
|---|---|---|
| Interaction | interaction_response | Bot replied to an interaction |
| Message | message_sent | Bot sent/edited/deleted a message via REST |
| Gateway | gateway_message | Gateway WebSocket activity (client to server) |
| Dispatch | dispatch | Event dispatched to client (server to client) |
waitForMessage internally filters for message_sent type actions. Use waitForAction with interaction_response for slash command responses.
Complete Action Type Reference
Message Actions:
| Type | Description |
|---|---|
message_sent | Message created (used by waitForMessage) |
message_edited | Message content updated |
message_deleted | Message removed |
message_pinned | Message pinned to channel |
message_unpinned | Message unpinned |
message_crossposted | Message published to following channels |
messages_bulk_deleted | Multiple messages deleted |
Interaction Actions:
| Type | Description |
|---|---|
interaction_response | Bot replied to interaction |
interaction_followup | Followup message sent |
interaction_edit | Interaction response edited |
Channel Actions:
| Type | Description |
|---|---|
channel_created | New channel created |
channel_updated | Channel settings changed |
channel_deleted | Channel removed |
channels_positions_updated | Channel positions reordered |
channel_overwrite_updated | Permission overwrite changed |
channel_overwrite_deleted | Permission overwrite removed |
Thread Actions:
| Type | Description |
|---|---|
thread_created | New thread started |
thread_updated | Thread settings changed |
thread_deleted | Thread removed |
thread_member_added | User joined thread |
thread_member_removed | User left thread |
Role & Member Actions:
| Type | Description |
|---|---|
role_created | New role created |
role_updated | Role settings changed |
role_deleted | Role removed |
role_positions_updated | Role positions reordered |
guild_member_added | User joined guild |
guild_member_updated | Member updated |
guild_member_removed | User left guild |
member_role_added | Role added to member |
member_role_removed | Role removed from member |
member_updated | Member properties changed |
member_kicked | Member was kicked |
Reaction Actions:
| Type | Description |
|---|---|
reaction_added | Reaction added to message |
reaction_removed | Reaction removed |
Voice Actions:
| Type | Description |
|---|---|
voice_state_updated | Voice state changed |
voice_channel_status_updated | Voice channel status changed |
Gateway Actions:
| Type | Description |
|---|---|
gateway_identify | Bot identified with gateway |
gateway_heartbeat | Heartbeat sent |
gateway_presence_update | Presence updated |
gateway_voice_state_update | Voice state update sent |
gateway_resume | Session resumed after disconnect |
gateway_message | Raw gateway message received |
gateway_request_guild_members | Guild members requested |
dispatch | Event dispatched to client |
Poll Actions:
| Type | Description |
|---|---|
poll_voters_fetched | Poll voters retrieved |
poll_expired | Poll ended |
Sticker Actions:
| Type | Description |
|---|---|
sticker_created | New sticker created |
sticker_updated | Sticker settings changed |
sticker_deleted | Sticker removed |
Webhook Actions:
| Type | Description |
|---|---|
webhook_created | New webhook created |
webhook_updated | Webhook settings changed |
webhook_deleted | Webhook removed |
webhook_executed | Webhook message sent |
Emoji Actions:
| Type | Description |
|---|---|
emoji_created | New emoji created |
emoji_updated | Emoji settings changed |
emoji_deleted | Emoji removed |
Stage Instance Actions:
| Type | Description |
|---|---|
stage_instance_created | Stage instance started |
stage_instance_updated | Stage instance settings changed |
stage_instance_deleted | Stage instance ended |
Ban Actions:
| Type | Description |
|---|---|
ban_created | User banned |
ban_removed | User unbanned |
bulk_ban | Multiple users banned |
Invite Actions:
| Type | Description |
|---|---|
invite_created | Invite created |
invite_deleted | Invite deleted or revoked |
Scheduled Event Actions:
| Type | Description |
|---|---|
scheduled_event_created | Scheduled event created |
scheduled_event_updated | Scheduled event updated |
scheduled_event_deleted | Scheduled event deleted |
AutoMod Actions:
| Type | Description |
|---|---|
automod_rule_created | AutoMod rule created |
automod_rule_updated | AutoMod rule updated |
automod_rule_deleted | AutoMod rule deleted |
Other Actions:
| Type | Description |
|---|---|
typing_started | Typing indicator sent |
rest_request | Generic REST API request |
dm_channel_opened | DM channel created |
guild_update | Guild settings changed |
Additional APIs
recordAssertion
Record an assertion result to the test registry for UI persistence:
import { recordAssertion } from '@robojs/mock/testing'
recordAssertion(sessionId, {
description: 'Response contains greeting',
passed: true,
expected: 'Hello',
actual: 'Hello',
diff: undefined
})import { recordAssertion } from '@robojs/mock/testing'
recordAssertion(sessionId, {
description: 'Response contains greeting',
passed: true,
expected: 'Hello',
actual: 'Hello',
diff: undefined
})Parameters:
| Parameter | Type | Description |
|---|---|---|
sessionId | string | Session to record assertion for |
result | AssertionResult | Assertion result object |
Assertions are automatically recorded by expectAction and expectNoAction. Use this directly when implementing custom assertion logic.
createSession
Lower-level session creation without test file tracking:
import { createSession } from '@robojs/mock/testing'
const session = await createSession({
name: 'my-session',
config: {
botUser: { username: 'TestBot' },
guilds: [{ name: 'Test Guild' }]
}
})
console.log(session.id) // Session ID
console.log(session.token) // Bot tokenimport { createSession } from '@robojs/mock/testing'
const session = await createSession({
name: 'my-session',
config: {
botUser: { username: 'TestBot' },
guilds: [{ name: 'Test Guild' }]
}
})
console.log(session.id) // Session ID
console.log(session.token) // Bot tokenUnlike createTestSession, this does not register the session with the test file registry. Use createTestSession for typical test scenarios and createSession when you need manual control over registry tracking.
Parameters:
| Parameter | Type | Description |
|---|---|---|
config | CreateTestSessionConfig | Optional session configuration |
Returns: Promise<{ id, token, botUser, guilds, channels, guildId }>
DEFAULT_MOCK_CONFIG
The default MockConfig constant used as a fallback when no explicit configuration is set:
import { DEFAULT_MOCK_CONFIG } from '@robojs/mock/testing'
console.log(DEFAULT_MOCK_CONFIG)
// {
// baseUrl: 'http://localhost:3000',
// controlUrl: 'http://localhost:3000/api/control',
// restUrl: 'http://localhost:3000/api',
// gatewayUrl: 'ws://localhost:3000',
// defaultTimeout: 5000
// }import { DEFAULT_MOCK_CONFIG } from '@robojs/mock/testing'
console.log(DEFAULT_MOCK_CONFIG)
// {
// baseUrl: 'http://localhost:3000',
// controlUrl: 'http://localhost:3000/api/control',
// restUrl: 'http://localhost:3000/api',
// gatewayUrl: 'ws://localhost:3000',
// defaultTimeout: 5000
// }These are fallback values. In practice, getMockConfig() builds URLs dynamically using the ROBO_MOCK_PORT environment variable or server info file.
Types
StartMockRoboOptions
Options for startMockRobo:
interface StartMockRoboOptions {
name?: string // Session name
port?: number // Mock server port (auto-discovered if not set)
timeout?: number // Bot connection timeout in ms (default: 30000)
verbose?: boolean // Show logs in console
testFilePath?: string // Test file path for registry tracking
logFile?: boolean | string // Enable file logging (true = auto, string = path)
logLevel?: 'trace' | 'debug' | 'info' | 'warn' | 'error' // Log level (default: 'debug')
hmr?: boolean // Enable HMR mode (spawns child process)
}// StartMockRoboOptions shape:
// {
// name: string, // Session name
// port: number, // Mock server port (auto-discovered if not set)
// timeout: number, // Bot connection timeout in ms (default: 30000)
// verbose: boolean, // Show logs in console
// testFilePath: string, // Test file path for registry tracking
// logFile: boolean | string, // Enable file logging (true = auto, string = path)
// logLevel: string, // Log level (default: 'debug')
// hmr: boolean // Enable HMR mode (spawns child process)
// }TestUtils
Object returned by createTestUtils:
interface TestUtils {
users: TestUsers // User management utilities
interactions: TestInteractions // Interaction simulation utilities
}// TestUtils shape:
// {
// users: TestUsers, // User management utilities
// interactions: TestInteractions // Interaction simulation utilities
// }MockMessage
Message returned from getChannelMessages:
interface MockMessage {
id: string
content: string
author: {
id: string
username?: string
bot?: boolean
}
[key: string]: unknown // Additional fields vary by message type
}// MockMessage shape:
// {
// id: string,
// content: string,
// author: { id, username, bot },
// ...additionalFields // Additional fields vary by message type
// }AssertionResult
Result of a single assertion, used for UI persistence:
interface AssertionResult {
description: string // Human-readable description
passed: boolean // Whether the assertion passed
expected: unknown // Expected value
actual: unknown // Actual value
diff?: string // Human-readable diff for UI display
}// AssertionResult shape:
// {
// description: string, // Human-readable description
// passed: boolean, // Whether the assertion passed
// expected: any, // Expected value
// actual: any, // Actual value
// diff: string // Human-readable diff for UI display
// }Protocol Constants
Internal constants used by Mock Server:
| Constant | Value | Description |
|---|---|---|
| Gateway Version | 10 | Discord Gateway API version |
| Heartbeat Interval | 41250ms | Gateway heartbeat timing |
| Voice Gateway Port | 50001 | Separate port for voice (WSS) |
| Session TTL | 3600000ms | 1 hour default |
| Cleanup Interval | 60000ms | Session cleanup check |
| Max Actions | 10000 | Per-session action limit |
| LRU Eviction | 10% | Oldest actions removed |
| Stage Buffer | 1000 | Event replay buffer |
| Loop Threshold | 10/sec | Events before detection |
| Loop Cooldown | 5000ms | Pause after loop detected |
Activity Commands
Stage WebSocket commands for controlling activity testing. These commands are sent through the Stage WS connection, not the testing API.
launch_activity
Launch an activity in the current session:
// Via Stage WS
{
command: 'launch_activity',
data: {
launch_url: 'http://localhost:5173',
application_id: '1234567890',
guild_id: guildId,
channel_id: channelId
}
}// Via Stage WS
{
command: 'launch_activity',
data: {
launch_url: 'http://localhost:5173',
application_id: '1234567890',
guild_id: guildId,
channel_id: channelId
}
}Returns frame_id, instance_id, proxy_origin, iframe_url, and sdk_shim_enabled.
close_activity
Close the running activity for the current session:
{
command: 'close_activity'
}{
command: 'close_activity'
}The session is determined from the WebSocket connection. No data payload is required.
activity_rpc
Forward an RPC message to or from the activity:
{
command: 'activity_rpc',
data: {
message: [1, { cmd: 'GET_USER', nonce: 'abc123' }],
frame_id: frameId,
instance_id: instanceId
}
}{
command: 'activity_rpc',
data: {
message: [1, { cmd: 'GET_USER', nonce: 'abc123' }],
frame_id: frameId,
instance_id: instanceId
}
}activity_set_auth_settings
Change the authentication simulation mode:
{
command: 'activity_set_auth_settings',
data: {
mode: 'manual' // 'auto_approve' | 'auto_deny' | 'manual'
}
}{
command: 'activity_set_auth_settings',
data: {
mode: 'manual' // 'auto_approve' | 'auto_deny' | 'manual'
}
}activity_set_platform_state
Update platform state to trigger SDK events:
{
command: 'activity_set_platform_state',
data: {
layout_mode: 1, // 0=focused, 1=pip, 2=grid
screen_orientation: 1, // numeric screen orientation
orientation: 'landscape', // 'portrait' | 'landscape'
thermal_state: 0 // 0=nominal, 1=fair, 2=serious, 3=critical
}
}{
command: 'activity_set_platform_state',
data: {
layout_mode: 1, // 0=focused, 1=pip, 2=grid
screen_orientation: 1, // numeric screen orientation
orientation: 'landscape', // 'portrait' | 'landscape'
thermal_state: 0 // 0=nominal, 1=fair, 2=serious, 3=critical
}
}Triggers ACTIVITY_LAYOUT_MODE_UPDATE, ORIENTATION_UPDATE, and/or THERMAL_STATE_UPDATE events for subscribed activities.
activity_set_iap_state
Update in-app purchase state:
{
command: 'activity_set_iap_state',
data: {
skus: [{ id: '123', name: 'Item', type: 5, price: { amount: 499, currency: 'usd' } }],
entitlements: [{ id: '456', sku_id: '123', user_id: userId }]
}
}{
command: 'activity_set_iap_state',
data: {
skus: [{ id: '123', name: 'Item', type: 5, price: { amount: 499, currency: 'usd' } }],
entitlements: [{ id: '456', sku_id: '123', user_id: userId }]
}
}activity_set_url_mappings
Update URL mapping rules:
{
command: 'activity_set_url_mappings',
data: {
url_mappings: [
{ prefix: '/api', target: 'localhost:8080' }
]
}
}{
command: 'activity_set_url_mappings',
data: {
url_mappings: [
{ prefix: '/api', target: 'localhost:8080' }
]
}
}activity_set_csp_mode
Switch Content Security Policy mode:
{
command: 'activity_set_csp_mode',
data: {
mode: 'discord_strict' // 'relaxed' | 'discord_strict'
}
}{
command: 'activity_set_csp_mode',
data: {
mode: 'discord_strict' // 'relaxed' | 'discord_strict'
}
}activity_emit_event
Manually emit an SDK event to the activity:
{
command: 'activity_emit_event',
data: {
event_name: 'ACTIVITY_LAYOUT_MODE_UPDATE',
data: { layout_mode: 1 }
}
}{
command: 'activity_emit_event',
data: {
event_name: 'ACTIVITY_LAYOUT_MODE_UPDATE',
data: { layout_mode: 1 }
}
}Useful for testing specific event handlers without triggering the full state change flow.
Activity commands require a running activity in the session. Launch an activity first with launch_activity before sending other activity commands.
Package Exports
@robojs/mock provides five export paths for different use cases:
| Import Path | Purpose |
|---|---|
@robojs/mock | Core classes, factories, and constants (Session, SessionManager, MockServerState, factory functions, snowflake utilities) |
@robojs/mock/session | Session-related classes for advanced usage (Session, SessionManager, InMemoryStorage, ActionRecorder, RecordingPlayer, MockServerState) |
@robojs/mock/testing | Testing utilities and Control API helpers (createTestSession, dispatchEvent, expectAction, waitForAction, etc.) |
@robojs/mock/testing/jest-reporter | Custom Jest reporter for tracking test results in Stage UI |
@robojs/mock/types | TypeScript type definitions (Session, SessionConfig, ActionType, ScenarioDefinition, etc.) |
Most test files only need the testing export:
import {
createTestSession,
dispatchEvent,
expectAction,
waitForAction
} from '@robojs/mock/testing'import {
createTestSession,
dispatchEvent,
expectAction,
waitForAction
} from '@robojs/mock/testing'For custom integrations or advanced scenarios, import from the core or session paths:
import { sessionManager, createMockGuild } from '@robojs/mock'
import { MockServerState } from '@robojs/mock/session'
import type { ActionType, ScenarioDefinition } from '@robojs/mock/types'import { sessionManager, createMockGuild } from '@robojs/mock'
import { MockServerState } from '@robojs/mock/session'