Popii ships a testing sub-package that lets you unit-test commands without a Discord connection or live bot. Tests run in Bun's built-in test runner.
// No extra install needed — it's included with popii
import { createTestClient, runCommand } from "popii/testing";
// test/commands/ping.test.ts
import { describe, it, expect } from "bun:test";
import { createTestClient, runCommand } from "popii/testing";
import pingCommand from "../../src/commands/ping";
describe("/ping", () => {
it("replies with Pong!", async () => {
const client = createTestClient();
const result = await runCommand(pingCommand, { client });
expect(result.replies[0].content).toContain("Pong!");
});
});
import { runCommand, mockOptions } from "popii/testing";
import greetCommand from "../../src/commands/greet";
it("greets the specified user", async () => {
const client = createTestClient();
const result = await runCommand(greetCommand, {
client,
options: mockOptions({ user: { id: "123", username: "Alice" } })
});
expect(result.replies[0].content).toContain("Alice");
});
it("rejects when not premium", async () => {
const client = createTestClient();
const result = await runCommand(premiumCommand, {
client,
locals: { dbUser: { premium: false, balance: 0 } }
});
expect(result.replies[0].ephemeral).toBe(true);
expect(result.replies[0].content).toContain("Premium users only");
});
Pass an array of middlewares to run the full pipeline:
import { middleware } from "popii";
import myMiddleware from "../../src/middlewares/02-load-user";
it("loads the user before running the command", async () => {
const client = createTestClient();
const result = await runCommand(someCommand, {
client,
middlewares: [myMiddleware],
locals: {}
});
expect(result.replies[0].content).toBeDefined();
});
it("defers and then replies", async () => {
const client = createTestClient();
const result = await runCommand(slowCommand, { client });
expect(result.deferred).toBe(true);
expect(result.replies[0]).toBeDefined();
});
bun test # run all tests
bun test test/commands/ping.test.ts # run a specific file
bun test --watch # re-run on file changes
Or use the CLI generator to scaffold a test file:
popii g test ping
# creates test/commands/ping.test.ts with boilerplate