UNPKG

every-plugin

Version:
163 lines (130 loc) 4.59 kB
--- name: plugin-testing description: Test every-plugin modules with vitest and the plugin runtime. Use when writing or modifying plugin tests under plugins/*/src/__tests__/ or plugins/*/tests/. metadata: sources: "src/testing/index.ts,src/runtime/index.ts" --- # every-plugin Testing ## Test Structure ``` plugins/your-plugin/ ├── src/__tests__/ │ ├── integration.test.ts # Full plugin lifecycle via runtime │ └── unit.test.ts # Service class in isolation └── vitest.config.ts ``` ## Unit Tests (Service Only) Test the service class directly without the plugin runtime: ```typescript import { describe, expect, it } from "vitest"; import { Effect } from "every-plugin/effect"; import { MyService } from "../service"; describe("MyService", () => { const service = new MyService("https://api.example.com", "test-key"); it("ping returns ok", async () => { const result = await Effect.runPromise(service.ping()); expect(result.status).toBe("ok"); }); it("getById returns item", async () => { const result = await Effect.runPromise(service.getById("item-1")); expect(result.item.id).toBe("item-1"); }); it("getById throws on missing item", async () => { await expect(Effect.runPromise(service.getById("missing"))).rejects.toThrow(); }); }); ``` ## Integration Tests (Plugin Runtime) Test the full plugin lifecycle — initialization, router execution, shutdown: ```typescript import { describe, expect, it, beforeAll, afterAll } from "vitest"; import { createPluginRuntime } from "every-plugin"; import Plugin from "../index"; describe("MyPlugin integration", () => { let runtime: ReturnType<typeof createPluginRuntime>; let client: any; beforeAll(async () => { runtime = createPluginRuntime({ registry: { "my-plugin": { module: Plugin }, }, }); const result = await runtime.usePlugin("my-plugin", { variables: { baseUrl: "https://api.example.com" }, secrets: { apiKey: "test-key" }, }); client = result.createClient(); }); afterAll(async () => { await runtime.shutdown(); }); it("ping responds", async () => { const result = await client.ping(); expect(result.status).toBe("ok"); }); it("getById returns item", async () => { const result = await client.getById({ id: "item-1" }); expect(result.item.id).toBe("item-1"); }); it("getById without auth throws UNAUTHORIZED", async () => { await expect(client.getById({ id: "item-1" })).rejects.toThrow(); }); }); ``` ## Testing Plugin Composition When testing an API plugin that uses `withPlugins`, mock the `pluginsClient`: ```typescript import { createPluginRuntime } from "every-plugin"; import ApiPlugin from "../index"; const mockRegistryClient = { getRegistryStatus: vi.fn().mockResolvedValue({ status: "ok" }), listRegistryApps: vi.fn().mockResolvedValue({ apps: [] }), }; describe("API with mock registry", () => { it("pluginDemo returns composed data", async () => { const runtime = createPluginRuntime({ registry: { api: { module: ApiPlugin }, }, }); const result = await runtime.usePlugin( "api", { variables: {}, secrets: {} }, { registry: () => mockRegistryClient }, ); const client = result.createClient(); const data = await client.pluginDemo(); expect(data.registryStatus.status).toBe("ok"); await runtime.shutdown(); }); }); ``` ## Testing Streaming (eventIterator) Use `for await` to collect streaming results: ```typescript it("search streams results", async () => { const chunks: any[] = []; const stream = await client.search({ query: "test", limit: 5 }); for await (const chunk of stream) { chunks.push(chunk); } expect(chunks.length).toBeGreaterThan(0); }); ``` ## Vitest Config ```typescript import { defineConfig } from "vitest/config"; import tsconfigPaths from "vite-tsconfig-paths"; export default defineConfig({ plugins: [tsconfigPaths()], test: { include: ["src/__tests__/**/*.test.ts"], }, }); ``` ## Common Mistakes - Not calling `runtime.shutdown()` in `afterAll` — leaves plugin scopes/resources running - Using `vi.fn()` without `.mockResolvedValue()` — unhandled promise rejections - Forgetting `vite-tsconfig-paths` plugin — path aliases like `every-plugin/orpc` won't resolve - Omitting the `registry` object when calling `createPluginRuntime(...)` — the runtime requires explicit plugin entries - Testing only happy paths — always test error channels (Effect failures, ORPCError throws)