Guides
Test without a radio
Drive the bot in Vitest on a fake radio, with simulated time.
The owner announces, the bot posts. A stranger tries, the bot refuses. Both checked in a test, without a radio.
import { createTestClient } from '@meshcorejs/testing';
import { afterEach, expect, it } from 'vitest';
const bots: Array<{ destroy(): Promise<void> }> = [];
afterEach(async () => {
await Promise.all(bots.map((bot) => bot.destroy()));
});
it('lets an owner post on the channel', async () => {
const bot = await createTestClient({ load: import.meta.dirname, now: '2026-09-20T18:00:00Z' });
bots.push(bot);
const alice = bot.fakeContact('Alice');
bot.fakeChannel('#club');
bot.setRoleMembers('owner', [alice]);
expect(await bot.dm(alice, '/announce Meeting at 19:00')).toEqual(['Posted']);
expect(bot.sentTo('#club')).toEqual(['Meeting at 19:00']);
});
it('refuses everyone else', async () => {
const bot = await createTestClient({ load: import.meta.dirname });
bots.push(bot);
expect(await bot.dm(bot.fakeContact('Bob'), '/announce free beer')).toEqual(['⛔ Permission denied']);
});What happens
createTestClient() logs a real Client in on a FakeRadio and loads the bricks of a folder.
fakeContact() and fakeChannel() fill the radio's tables.
dm() sends a message as a contact and returns what the bot answered. sentTo() returns what the bot
put on a channel.
Time is simulated. advanceTime('6h') fires the jobs due in between. destroy() releases everything.
Go further
- @meshcorejs/testing for the package and its reference.
- TestBot for every helper.