Getting started
Install
Shell
npm install --save-dev neritest-js
npm install @nerimity/nerimity.js
Node 18+. Works with any test runner (or none - the bundled expect throws on failure).
Your first test
NeriTest doesn't care which runner you use. Here it is with Node's built-in node:test:
TypeScript
import { test, after } from "node:test";
import assert from "node:assert/strict";
import { createSandbox } from "neritest-js";
test("bot replies to !ping", async () => {
const sandbox = createSandbox();
const server = sandbox.createServer({ name: "Dev" });
const channel = server.createChannel({ name: "general" });
const alice = sandbox.createUser({ username: "Alice" });
server.addMember(alice);
const client = await sandbox.createClient();
client.on("messageCreate", async (m: any) => {
if (m.user.id === client.user.id) return;
if (m.content === "!ping") await m.reply("Pong!");
});
await new Promise((r) => client.on("ready", r));
channel.send(alice, "!ping");
// poll until the reply lands (it round-trips through the real REST API)
await waitFor(() => channel.lastMessage?.content === "Pong!");
assert.equal(channel.lastMessage?.content, "Pong!");
await sandbox.close();
});
function waitFor(fn: () => unknown, timeout = 3000) {
return new Promise<void>((resolve, reject) => {
const start = Date.now();
const tick = () => {
if (fn()) return resolve();
if (Date.now() - start > timeout) return reject(new Error("timed out"));
setTimeout(tick, 10);
};
tick();
});
}
Mental model
There are two sides:
- The world - you, the test author, driving
sandbox.*and the facade objects (SandboxServer,SandboxChannel,SandboxUser,SandboxMessage). When Alice sends a message, that's the world. - The bot - the real nerimity.js
Client. It receives gateway events and makes REST calls, exactly as it would against production.
Because the two sides communicate over real sockets/HTTP, things are asynchronous. After
a world action, await or poll for the bot's reaction rather than asserting synchronously.
Things to remember
await sandbox.ready()(orawait sandbox.createClient(), which awaits it) before a bot connects. World-building (createServer,channel.send) works immediately.- Create servers/channels before the bot connects when you can - they arrive in the auth
payload and are cached. If you create them after, wait for the SDK cache to populate
(
waitFor(() => client.servers.cache.get(id))) before using SDK objects. - Guard the bot's own messages with
if (message.user.id === client.user.id) return;- the bot receives its own sends over the gateway, just like production. - Always
await sandbox.close()so ports are freed and the bot's socket stops reconnecting.
Next: the cookbook for recipes, or the API reference.