Test your bot without connecting to Discord. The @djs-core/testing package ships with @djs-core/dev — no extra install step.

Use Bun’s native test runner (bun:test). Colocate tests next to your source files: ping.ts + ping.test.ts.

Quick start

Most tests need one line of setup — no Discord mock, no manual client.db wiring:

  1. Colocate a test file

    src/interactions/commands/ping.test.ts
    import { expect, test } from "bun:test";
    import { testCommand } from "@djs-core/testing";
    import pingCommand from "./ping";
    test("ping replies with pong", async () => {
    const res = await testCommand(pingCommand);
    expect(res.repliedWith).toContain("Pong!");
    });
  2. Run tests

    Terminal window
    djs-core test
    # or
    bun test

Zero-config client stubs

testCommand (and other helpers) attach a mock interaction.client automatically:

API Default stub
client.db.get() { val: 1 }
client.db.execute() []
client.myPlugin.anyMethod() "test"
client.anything.you.call() "test"

So a command that uses client.db and a plugin works out of the box — you only pass client when you need a specific return value for your assertion.

// Enough for most smoke tests
await testCommand(pingCommand);
// Override only what you assert on
await testCommand(myCommand, {
client: {
demo: { sayHello: () => "bonjour" },
},
});
expect(res.repliedWith).toContain("bonjour");

Auto-stubs are intentionally dumb ("test", { val: 1 }). They keep commands from crashing — not a substitute for testing real DB logic. Override client.db when the query result matters.

Helpers

Helper Use for
testCommand Slash commands
testButton Buttons
testModal Modals
testSelectMenu Select menus
testContextMenu Context menus
testAutocomplete Autocomplete options
testEvent Event listeners

Slash command options

await testCommand(echoCommand, {
options: { msg: "hello" },
});

Button with payload

await testButton(myButton, {
data: { userId: "123" },
});
await testModal(myModal, {
fields: { username: "grug" },
});

Autocomplete

const res = await testAutocomplete(searchCommand, {
focused: { name: "q", value: "pi" },
});
expect(res.respondedWith).toEqual([{ name: "pi", value: "pi" }]);

Handler routing

By default, helpers call .execute() directly. To exercise routing (subcommands, custom IDs):

await testCommand(command, {
viaHandler: true,
route: "config.user",
});

Assertions

Each helper returns:

  • repliedWith — last reply content (string)
  • replies — all reply/followUp/editReply/defer calls
  • deferred — whether deferReply() was called
  • respondedWith — autocomplete choices (autocomplete only)
const res = await testCommand(slowCommand);
expect(res.deferred).toBe(true);
expect(res.repliedWith).toBe("done");

Low-level mocks

For advanced cases, use factories directly:

import {
createMockChatInputInteraction,
createReplyTracker,
mockClient,
} from "@djs-core/testing";