Setup
Import testing utilities from@jelou/functions/testing:
import {
createMockContext,
createMockCronContext,
createMockEventContext,
createMockRequest,
createMockPlatformRequest,
createMockJelouClient,
createMockMemoryClient,
} from "@jelou/functions/testing";
createMockContext(overrides?)
Creates a context with sensible defaults for testing.
const ctx = createMockContext();
const ctx2 = createMockContext({
company: { id: 42, name: "Shop ABC" },
user: { id: 99, names: "Maria Garcia" },
bot: { id: "bot-123", name: "Support Bot", channel: "whatsapp" },
env: {
get: (key) => key === "CRM_API_KEY" ? "sk-test-123" : undefined,
has: (key) => key === "CRM_API_KEY",
toObject: () => ({ CRM_API_KEY: "sk-test-123" }),
},
});
createMockCronContext(expression, overrides?, cronName?)
Creates a context with isCron: true and trigger.type: "cron".
const ctx = createMockCronContext("0 9 * * *");
// ctx.isCron === true
// ctx.trigger === { type: "cron", cron: "0 9 * * *" }
// ctx.isHttp === false
const ctx2 = createMockCronContext("0 9 * * *", {}, "daily-reminder");
// ctx2.trigger === { type: "cron", cron: "0 9 * * *", cronName: "daily-reminder" }
createMockEventContext(eventName, overrides?)
Creates a context with isEvent: true and trigger.type: "event".
const ctx = createMockEventContext("payment.completed");
// ctx.isEvent === true
// ctx.trigger === { type: "event", event: "payment.completed" }
createMockRequest(body?, options?)
Creates a standard Web Request object.
const req = createMockRequest();
// GET http://localhost:8000/
const req2 = createMockRequest({ phone: "593987654321" });
// POST with JSON body, Content-Type: application/json
const req3 = createMockRequest(null, {
method: "PUT",
url: "https://my-function.fn.jelou.ai/clients/42",
headers: { "x-api-key": "sk-test-123" },
});
createMockPlatformRequest(token, body?, options?)
Creates a Request with the X-Jelou-Token header. Useful for testing functions that receive requests authenticated with runtime tokens.
const req = createMockPlatformRequest("jfn_rt_test123");
// GET with X-Jelou-Token: jfn_rt_test123
const req2 = createMockPlatformRequest("jfn_rt_test123", { query: "test" });
// POST with JSON body + X-Jelou-Token: jfn_rt_test123
const req3 = createMockPlatformRequest("jfn_rt_test123", null, {
method: "PUT",
url: "https://my-function.fn.jelou.ai/admin",
headers: { "x-custom": "value" },
});
// PUT with X-Jelou-Token + additional headers
createMockJelouClient(options?)
Creates a mock messaging client with call recording.
const mockJelou = createMockJelouClient();
const ctx = createMockContext({ jelou: mockJelou });
await ctx.jelou.send({ type: "text", to: "+593987654321", text: "Hello" });
// Inspect calls
mockJelou.calls.length; // 1
mockJelou.calls[0].method; // "send"
mockJelou.calls[0].args.type; // "text"
mockJelou.calls[0].timestamp; // Date.now()
Custom results
const mockJelou = createMockJelouClient({
sendResult: { messageId: "msg-custom-123" },
templateResult: [{ id: "tmpl-1", destination: "+593987654321" }],
});
Reset between tests
mockJelou.reset(); // clears recorded calls
createMockMemoryClient(options?)
Creates a mock memory client with an in-memory store.
const mockMemory = createMockMemoryClient({
store: { step: "start", attempts: 0 },
});
const ctx = createMockContext({ memory: mockMemory });
const step = await ctx.memory.get("step", "unknown"); // "start"
await ctx.memory.set("step", "confirm", 3600);
Inspect calls
mockMemory.calls.length; // 2 (get + set)
mockMemory.calls[1].method; // "set"
mockMemory.calls[1].args; // { key: "step", value: "confirm", ttl: 3600 }
Reset between tests
mockMemory.reset(); // clears calls and resets store
Full example
index.test.ts
import { assertEquals } from "jsr:@std/assert";
import { define, z } from "@jelou/functions";
import {
createMockContext,
createMockCronContext,
createMockRequest,
createMockPlatformRequest,
createMockJelouClient,
createMockMemoryClient,
} from "@jelou/functions/testing";
const fn = define({
name: "lookup-customer",
description: "Looks up customer info by phone",
input: z.object({
phone: z.string().min(10),
}),
output: z.object({
name: z.string(),
plan: z.string(),
companyId: z.number(),
}),
handler: async (input, ctx) => {
return {
name: "Maria Garcia",
plan: "Premium",
companyId: ctx.company.id,
};
},
});
Deno.test("returns customer info with correct company ID", async () => {
const ctx = createMockContext({
company: { id: 42, name: "Shop ABC" },
});
const req = createMockRequest({ phone: "593987654321" });
const result = await fn.handler({ phone: "593987654321" }, ctx, req);
assertEquals(result.name, "Maria Garcia");
assertEquals(result.companyId, 42);
});
Deno.test("cron skip when not a cron trigger", async () => {
const cronFn = define({
name: "daily-cleanup",
input: z.object({}),
config: { cron: [{ expression: "0 3 * * *" }] },
handler: async (_input, ctx) => {
if (!ctx.isCron) return { skipped: true };
return { cleaned: true };
},
});
const httpCtx = createMockContext();
const req = createMockRequest();
assertEquals(await cronFn.handler({}, httpCtx, req), { skipped: true });
const cronCtx = createMockCronContext("0 3 * * *");
assertEquals(await cronFn.handler({}, cronCtx, req), { cleaned: true });
});
Deno.test("messaging mock records calls", async () => {
const mockJelou = createMockJelouClient();
const ctx = createMockContext({ jelou: mockJelou });
await ctx.jelou.send({
type: "text",
to: "+593987654321",
text: "Your order is ready",
});
assertEquals(mockJelou.calls.length, 1);
assertEquals(mockJelou.calls[0].method, "send");
assertEquals(mockJelou.calls[0].args.type, "text");
});
Deno.test("memory mock persists values", async () => {
const mockMemory = createMockMemoryClient({ store: { step: "start" } });
const ctx = createMockContext({ memory: mockMemory });
assertEquals(await ctx.memory.get("step", "unknown"), "start");
await ctx.memory.set("step", "confirm", 3600);
assertEquals(await ctx.memory.get("step", "unknown"), "confirm");
});
Deno.test("platform request includes token header", () => {
const req = createMockPlatformRequest("jfn_rt_test123", { action: "test" });
assertEquals(req.headers.get("x-jelou-token"), "jfn_rt_test123");
assertEquals(req.method, "POST");
});
createMockApp(tools, config?)
Creates a mock EdgeApp for testing multi-tool functions.
import { assertEquals } from "jsr:@std/assert";
import { createMockApp, createMockContext, createMockRequest } from "@jelou/functions/testing";
import { define, z } from "@jelou/functions";
const myApp = createMockApp({
sendEmail: define({
description: "Sends an email",
input: z.object({ to: z.string() }),
handler: async (input) => ({ sent: true }),
}),
readInbox: define({
description: "Reads inbox messages",
input: z.object({}),
handler: async () => ({ messages: [] }),
}),
});
Deno.test("app has correct tools", () => {
assertEquals(Object.keys(myApp.tools), ["sendEmail", "readInbox"]);
assertEquals(myApp.__jelou_edge_app, true);
});
Deno.test("each tool works independently", async () => {
const ctx = createMockContext();
const req = createMockRequest({ to: "[email protected]" });
const result = await myApp.tools.sendEmail.handler(
{ to: "[email protected]" },
ctx,
req,
);
assertEquals(result, { sent: true });
});
Run tests with Deno:
deno test