| 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:
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:
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:
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:
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
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)