Testing Commands
Test slash commands and autocomplete
Test your slash commands systematically with Mock Server. Verify responses, check option handling, and test autocomplete behavior.
Examples on this page assume you have a test session set up. See Automated Testing for session setup and Interaction Type Constants for a reference of numeric type values used below.
Stage UI showing a slash command being executed and the bot's response with an embed displayed in the message area
Basic Commands
Simple Command
Test a command with no options:
import { dispatchInteraction, expectAction } from '@robojs/mock/testing'
it('responds to /ping', async () => {
await dispatchInteraction(session.id, {
type: 2, // APPLICATION_COMMAND
data: {
name: 'ping',
type: 1 // CHAT_INPUT
},
channel_id: session.channels[0].id,
guild_id: session.guildId
})
await expectAction(session.id, {
description: 'Bot should respond with pong',
type: 'interaction_response',
expected: {
response_data: {
content: expect.stringContaining('Pong')
}
}
})
})import { dispatchInteraction, expectAction } from '@robojs/mock/testing'
it('responds to /ping', async () => {
await dispatchInteraction(session.id, {
type: 2, // APPLICATION_COMMAND
data: {
name: 'ping',
type: 1 // CHAT_INPUT
},
channel_id: session.channels[0].id,
guild_id: session.guildId
})
await expectAction(session.id, {
description: 'Bot should respond with pong',
type: 'interaction_response',
expected: {
response_data: {
content: expect.stringContaining('Pong')
}
}
})
})Response Types
Check different response behaviors:
// Ephemeral response (only visible to the user who triggered it)
await expectAction(session.id, {
description: 'Response should be ephemeral',
type: 'interaction_response',
expected: {
response_data: {
flags: 64 // EPHEMERAL
}
}
})
// Deferred response (thinking state)
await expectAction(session.id, {
description: 'Bot should defer',
type: 'interaction_response',
expected: {
response_type: 5 // DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE
}
})// Ephemeral response (only visible to the user who triggered it)
await expectAction(session.id, {
description: 'Response should be ephemeral',
type: 'interaction_response',
expected: {
response_data: {
flags: 64 // EPHEMERAL
}
}
})
// Deferred response (thinking state)
await expectAction(session.id, {
description: 'Bot should defer',
type: 'interaction_response',
expected: {
response_type: 5 // DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE
}
})Commands with Options
String Options
await dispatchInteraction(session.id, {
type: 2, // APPLICATION_COMMAND
data: {
name: 'echo',
type: 1, // CHAT_INPUT
options: [{
name: 'message',
type: 3, // STRING
value: 'Hello world'
}]
},
channel_id: session.channels[0].id
})
await expectAction(session.id, {
description: 'Bot should echo the message',
type: 'interaction_response',
expected: {
response_data: {
content: 'Hello world'
}
}
})await dispatchInteraction(session.id, {
type: 2, // APPLICATION_COMMAND
data: {
name: 'echo',
type: 1, // CHAT_INPUT
options: [{
name: 'message',
type: 3, // STRING
value: 'Hello world'
}]
},
channel_id: session.channels[0].id
})
await expectAction(session.id, {
description: 'Bot should echo the message',
type: 'interaction_response',
expected: {
response_data: {
content: 'Hello world'
}
}
})User Options
// These are mock IDs -- any snowflake-format string works for testing
const targetUserId = '111222333444555666'
const targetUsername = 'TestUser'
await dispatchInteraction(session.id, {
type: 2, // APPLICATION_COMMAND
data: {
name: 'info',
type: 1, // CHAT_INPUT
options: [{
name: 'user',
type: 6, // USER
value: targetUserId
}],
resolved: {
users: {
[targetUserId]: {
id: targetUserId,
username: targetUsername,
discriminator: '0',
avatar: null
}
}
}
},
channel_id: session.channels[0].id
})// These are mock IDs -- any snowflake-format string works for testing
const targetUserId = '111222333444555666'
const targetUsername = 'TestUser'
await dispatchInteraction(session.id, {
type: 2, // APPLICATION_COMMAND
data: {
name: 'info',
type: 1, // CHAT_INPUT
options: [{
name: 'user',
type: 6, // USER
value: targetUserId
}],
resolved: {
users: {
[targetUserId]: {
id: targetUserId,
username: targetUsername,
discriminator: '0',
avatar: null
}
}
}
},
channel_id: session.channels[0].id
})Channel Options
const channel = session.channels.find(c => c.name === 'announcements')
await dispatchInteraction(session.id, {
type: 2, // APPLICATION_COMMAND
data: {
name: 'announce',
type: 1, // CHAT_INPUT
options: [{
name: 'channel',
type: 7, // CHANNEL
value: channel.id
}],
resolved: {
channels: {
[channel.id]: {
id: channel.id,
name: channel.name,
type: channel.type
}
}
}
},
channel_id: session.channels[0].id
})const channel = session.channels.find(c => c.name === 'announcements')
await dispatchInteraction(session.id, {
type: 2, // APPLICATION_COMMAND
data: {
name: 'announce',
type: 1, // CHAT_INPUT
options: [{
name: 'channel',
type: 7, // CHANNEL
value: channel.id
}],
resolved: {
channels: {
[channel.id]: {
id: channel.id,
name: channel.name,
type: channel.type
}
}
}
},
channel_id: session.channels[0].id
})Number Options
await dispatchInteraction(session.id, {
type: 2, // APPLICATION_COMMAND
data: {
name: 'roll',
type: 1, // CHAT_INPUT
options: [{
name: 'sides',
type: 4, // INTEGER
value: 20
}]
},
channel_id: session.channels[0].id
})await dispatchInteraction(session.id, {
type: 2, // APPLICATION_COMMAND
data: {
name: 'roll',
type: 1, // CHAT_INPUT
options: [{
name: 'sides',
type: 4, // INTEGER
value: 20
}]
},
channel_id: session.channels[0].id
})Subcommands
Single Level
// /config set key:setting value:enabled
await dispatchInteraction(session.id, {
type: 2, // APPLICATION_COMMAND
data: {
name: 'config',
type: 1, // CHAT_INPUT
options: [{
name: 'set',
type: 1, // SUB_COMMAND
options: [
{ name: 'key', type: 3, value: 'setting' }, // STRING
{ name: 'value', type: 3, value: 'enabled' } // STRING
]
}]
},
channel_id: session.channels[0].id
})// /config set key:setting value:enabled
await dispatchInteraction(session.id, {
type: 2, // APPLICATION_COMMAND
data: {
name: 'config',
type: 1, // CHAT_INPUT
options: [{
name: 'set',
type: 1, // SUB_COMMAND
options: [
{ name: 'key', type: 3, value: 'setting' }, // STRING
{ name: 'value', type: 3, value: 'enabled' } // STRING
]
}]
},
channel_id: session.channels[0].id
})Subcommand Groups
// /moderation user ban target:@user reason:spam
const targetUserId = '111222333444555666' // Mock user ID
await dispatchInteraction(session.id, {
type: 2, // APPLICATION_COMMAND
data: {
name: 'moderation',
type: 1, // CHAT_INPUT
options: [{
name: 'user',
type: 2, // SUB_COMMAND_GROUP
options: [{
name: 'ban',
type: 1, // SUB_COMMAND
options: [
{ name: 'target', type: 6, value: targetUserId }, // USER
{ name: 'reason', type: 3, value: 'spam' } // STRING
]
}]
}]
},
channel_id: session.channels[0].id
})// /moderation user ban target:@user reason:spam
const targetUserId = '111222333444555666' // Mock user ID
await dispatchInteraction(session.id, {
type: 2, // APPLICATION_COMMAND
data: {
name: 'moderation',
type: 1, // CHAT_INPUT
options: [{
name: 'user',
type: 2, // SUB_COMMAND_GROUP
options: [{
name: 'ban',
type: 1, // SUB_COMMAND
options: [
{ name: 'target', type: 6, value: targetUserId }, // USER
{ name: 'reason', type: 3, value: 'spam' } // STRING
]
}]
}]
},
channel_id: session.channels[0].id
})Autocomplete
Testing Autocomplete
Autocomplete sends a special interaction type:
await dispatchInteraction(session.id, {
type: 4, // APPLICATION_COMMAND_AUTOCOMPLETE
data: {
name: 'search',
type: 1, // CHAT_INPUT
options: [{
name: 'query',
type: 3, // STRING
value: 'test',
focused: true // This option is being autocompleted
}]
},
channel_id: session.channels[0].id
})
await expectAction(session.id, {
description: 'Bot should return autocomplete choices',
type: 'interaction_response',
expected: {
response_type: 8, // APPLICATION_COMMAND_AUTOCOMPLETE_RESULT
response_data: {
choices: expect.arrayContaining([
expect.objectContaining({ name: expect.any(String) })
])
}
}
})await dispatchInteraction(session.id, {
type: 4, // APPLICATION_COMMAND_AUTOCOMPLETE
data: {
name: 'search',
type: 1, // CHAT_INPUT
options: [{
name: 'query',
type: 3, // STRING
value: 'test',
focused: true // This option is being autocompleted
}]
},
channel_id: session.channels[0].id
})
await expectAction(session.id, {
description: 'Bot should return autocomplete choices',
type: 'interaction_response',
expected: {
response_type: 8, // APPLICATION_COMMAND_AUTOCOMPLETE_RESULT
response_data: {
choices: expect.arrayContaining([
expect.objectContaining({ name: expect.any(String) })
])
}
}
})Verify Specific Choices
import { waitForAction } from '@robojs/mock/testing'
const actions = await waitForAction(session.id, {
type: 'interaction_response',
timeout: 5000
})
const choices = (actions[0].data as any)?.response_data?.choices
expect(choices).toContainEqual(
expect.objectContaining({
name: 'Test Item',
value: 'test-item'
})
)import { waitForAction } from '@robojs/mock/testing'
const actions = await waitForAction(session.id, {
type: 'interaction_response',
timeout: 5000
})
const choices = actions[0].data?.response_data?.choices
expect(choices).toContainEqual(
expect.objectContaining({
name: 'Test Item',
value: 'test-item'
})
)Stage UI showing the autocomplete dropdown with suggestion choices appearing as the user types a slash command option value
Context Menu Commands
User Context Menu
const targetUserId = '111222333444555666' // Mock user ID
const targetUsername = 'TestUser'
await dispatchInteraction(session.id, {
type: 2, // APPLICATION_COMMAND
data: {
name: 'Get User Info',
type: 2, // USER context menu
target_id: targetUserId,
resolved: {
users: {
[targetUserId]: {
id: targetUserId,
username: targetUsername,
discriminator: '0',
avatar: null
}
}
}
},
channel_id: session.channels[0].id
})const targetUserId = '111222333444555666' // Mock user ID
const targetUsername = 'TestUser'
await dispatchInteraction(session.id, {
type: 2, // APPLICATION_COMMAND
data: {
name: 'Get User Info',
type: 2, // USER context menu
target_id: targetUserId,
resolved: {
users: {
[targetUserId]: {
id: targetUserId,
username: targetUsername,
discriminator: '0',
avatar: null
}
}
}
},
channel_id: session.channels[0].id
})Message Context Menu
const messageId = '123456789' // Mock message ID
const authorId = '111222333444555666' // Mock author ID
const authorUsername = 'TestUser'
await dispatchInteraction(session.id, {
type: 2, // APPLICATION_COMMAND
data: {
name: 'Translate',
type: 3, // MESSAGE context menu
target_id: messageId,
resolved: {
messages: {
[messageId]: {
id: messageId,
content: 'Bonjour',
channel_id: session.channels[0].id,
author: { id: authorId, username: authorUsername }
}
}
}
},
channel_id: session.channels[0].id
})const messageId = '123456789' // Mock message ID
const authorId = '111222333444555666' // Mock author ID
const authorUsername = 'TestUser'
await dispatchInteraction(session.id, {
type: 2, // APPLICATION_COMMAND
data: {
name: 'Translate',
type: 3, // MESSAGE context menu
target_id: messageId,
resolved: {
messages: {
[messageId]: {
id: messageId,
content: 'Bonjour',
channel_id: session.channels[0].id,
author: { id: authorId, username: authorUsername }
}
}
}
},
channel_id: session.channels[0].id
})Stage UI showing a right-click context menu on a user or message with custom context menu commands visible in the Apps section
Permission Testing
Required Permissions
Test that commands respect permissions. The mock server generates a member for the dispatching user with empty roles by default, so a non-admin user is the default behavior:
// User without admin role (default behavior)
await dispatchInteraction(session.id, {
type: 2, // APPLICATION_COMMAND
data: { name: 'admin-only', type: 1 }, // CHAT_INPUT
channel_id: session.channels[0].id
})
await expectAction(session.id, {
description: 'Bot should deny access',
type: 'interaction_response',
expected: {
response_data: {
content: expect.stringContaining('permission')
}
}
})// User without admin role (default behavior)
await dispatchInteraction(session.id, {
type: 2, // APPLICATION_COMMAND
data: { name: 'admin-only', type: 1 }, // CHAT_INPUT
channel_id: session.channels[0].id
})
await expectAction(session.id, {
description: 'Bot should deny access',
type: 'interaction_response',
expected: {
response_data: {
content: expect.stringContaining('permission')
}
}
})Guild vs DM Commands
Guild Command
await dispatchInteraction(session.id, {
type: 2, // APPLICATION_COMMAND
data: { name: 'server-info', type: 1 }, // CHAT_INPUT
channel_id: session.channels[0].id,
guild_id: session.guildId // Include guild_id
})await dispatchInteraction(session.id, {
type: 2, // APPLICATION_COMMAND
data: { name: 'server-info', type: 1 }, // CHAT_INPUT
channel_id: session.channels[0].id,
guild_id: session.guildId // Include guild_id
})DM Command
// DM channels have no guild_id
await dispatchInteraction(session.id, {
type: 2, // APPLICATION_COMMAND
data: { name: 'help', type: 1 }, // CHAT_INPUT
channel_id: dmChannel.id
// No guild_id for DMs
})// DM channels have no guild_id
await dispatchInteraction(session.id, {
type: 2, // APPLICATION_COMMAND
data: { name: 'help', type: 1 }, // CHAT_INPUT
channel_id: dmChannel.id
// No guild_id for DMs
})