Skip to content

Commit 903aa42

Browse files
ianmacartneyConvex, Inc.
authored andcommitted
Ian/component test docs (#43745)
- Add sections for testing components GitOrigin-RevId: ec2e2658f52cad62bdcfbdbc11b8fb1f779109c6
1 parent 14f33bf commit 903aa42

File tree

2 files changed

+146
-0
lines changed

2 files changed

+146
-0
lines changed

npm-packages/docs/docs/components/authoring.mdx

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,3 +589,86 @@ export const count = query({
589589
590590
This explicit passing makes it clear what data flows between the app and
591591
component, making your component easier to understand and test.
592+
593+
## Testing
594+
595+
### Testing implementations
596+
597+
To test components, you can use the
598+
[`convex-test` library](/testing/convex-test.mdx). The main difference is that
599+
you must provide the schema and modules to the test instance.
600+
601+
```ts title="component/some.test.ts"
602+
import { test } from "vitest";
603+
import { convexTest } from "convex-test";
604+
import schema from "./schema.ts";
605+
const modules = import.meta.glob("./**/*.ts");
606+
607+
export function initConvexTest() {
608+
const t = convexTest(schema, modules);
609+
return t;
610+
}
611+
612+
test("Test something with a local component", async () => {
613+
const t = initConvexTest();
614+
// Test like you would normally.
615+
await t.run(async (ctx) => {
616+
await ctx.db.insert("myComponentTable", { name: "test" });
617+
});
618+
});
619+
```
620+
621+
If your component has child components, see the
622+
[Testing components](/components/using.mdx#testing-components) section in the
623+
Using Components documentation.
624+
625+
### Testing the API and client code
626+
627+
To test the functions that are exported from the component to run in the app's
628+
environment, you can follow the same approach as in
629+
[Using Components](/components/using.mdx#testing-components) and test it from an
630+
app that uses the component.
631+
632+
The template component includes an example app in part for this purpose: to
633+
exercise the component's bundled code as it will be used by apps installing it.
634+
635+
### Exporting test helpers
636+
637+
Most components export testing helpers to make it easy to register the component
638+
with the test instance. Here is an example from the
639+
[template component’s `/test` entrypoint](https://github.com/get-convex/templates/blob/main/template-component/src/test.ts):
640+
641+
```ts
642+
/// <reference types="vite/client" />
643+
import type { TestConvex } from "convex-test";
644+
import type { GenericSchema, SchemaDefinition } from "convex/server";
645+
import schema from "./component/schema.js";
646+
const modules = import.meta.glob("./component/**/*.ts");
647+
648+
/**
649+
* Register the component with the test convex instance.
650+
* @param t - The test convex instance, e.g. from calling `convexTest`.
651+
* @param name - The name of the component, as registered in convex.config.ts.
652+
*/
653+
export function register(
654+
t: TestConvex<SchemaDefinition<GenericSchema, boolean>>,
655+
name: string = "sampleComponent",
656+
) {
657+
t.registerComponent(name, schema, modules);
658+
}
659+
export default { register, schema, modules };
660+
```
661+
662+
For NPM packages, this is exposed as `@your/package/test` in the package's
663+
`package.json`:
664+
665+
```json
666+
{
667+
...
668+
"exports": {
669+
...
670+
"./test": "./src/test.ts",
671+
...
672+
}
673+
}
674+
```

npm-packages/docs/docs/components/using.mdx

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,69 @@ from certain components.
153153
/>
154154
</p>
155155

156+
## Testing components
157+
158+
When writing tests with [`convex-test`](/testing/convex-test.mdx), that use
159+
components, you must register the component with the test instance. This tells
160+
it what schema to validate and where to find the component source code. Most
161+
components export convenient helper functions on `/test` to make this easy:
162+
163+
```ts title="convex/some.test.ts"
164+
import agentTest from "@convex-dev/agent/test";
165+
import { expect, test } from "vitest";
166+
import { convexTest } from "convex-test";
167+
import { components } from "./_generated/api";
168+
import { createThread } from "@convex-dev/agent";
169+
170+
// Define this once, often in a shared test helper file.
171+
export function initConvexTest() {
172+
const t = convexTest();
173+
// highlight-next-line
174+
agentTest.register(t);
175+
return t;
176+
}
177+
178+
test("Agent createThread", async () => {
179+
const t = initConvexTest();
180+
181+
const threadId = await t.run(async (ctx) => {
182+
// Calling functions that use ctx and components.agent
183+
return await createThread(ctx, components.agent, {
184+
title: "Hello, world!",
185+
});
186+
});
187+
// Calling functions directly on the component's API
188+
const thread = await t.query(components.agent.threads.getThread, {
189+
threadId,
190+
});
191+
expect(thread).toMatchObject({
192+
title: "Hello, world!",
193+
});
194+
});
195+
```
196+
197+
If you need to register the component yourself, you can do so by passing the
198+
component's schema and modules to the test instance.
199+
200+
```ts title="convex/manual.test.ts"
201+
/// <reference types="vite/client" />
202+
import { test } from "vitest";
203+
import { convexTest } from "convex-test";
204+
import schema from "./path/to/component/schema.ts";
205+
const modules = import.meta.glob("./path/to/component/**/*.ts");
206+
207+
test("Test something with a local component", async () => {
208+
const t = convexTest();
209+
t.registerComponent("componentName", schema, modules);
210+
211+
await t.run(async (ctx) => {
212+
await ctx.runQuery(components.componentName.someQuery, {
213+
arg: "value",
214+
});
215+
});
216+
});
217+
```
218+
156219
## Log Streams
157220

158221
You can use the `data.function.component_path` field in

0 commit comments

Comments
 (0)