# @repo/core-testing Shared testing utilities. Tag: `tooling`. May be depended on by any package as a devDependency. ## Subpath exports - `@repo/core-testing/factory` — `defineFactory(builder)` for test data factories - `@repo/core-testing/contract` — `defineContractSuite(name, suite)` for cross-impl contract tests - `@repo/core-testing/react` — `renderWithProviders`, `createMockTrpcClient` - `renderWithProviders` does NOT include a tRPC provider. Consumers needing tRPC should wire their own TRPCProvider (from their app's tRPC client setup) and use `createMockTrpcClient` as the client. This constraint exists because tooling packages cannot import `AppRouter` from `@repo/core-api`. - `@repo/core-testing/payload` — `stubPayloadConfig`, `mockPayloadModule` - `@repo/core-testing/setup/jsdom` — vitest setupFile (jest-dom + cleanup) - `@repo/core-testing/setup/node` — vitest setupFile (no-op placeholder) ## Adding a factory ```typescript import { defineFactory } from "@repo/core-testing/factory"; export const articleFactory = defineFactory
(({ sequence }) => ({ id: `article-${sequence}`, title: `Article ${sequence}`, // stable defaults — overrides drive variation })); ``` ## Using createMockTrpcClient For component tests that consume tRPC procedures, mock the responses by procedure path (dot-separated): ```typescript import { createMockTrpcClient } from "@repo/core-testing/react"; import type { AppRouter } from "@repo/core-api"; // import in your app/feature, not in core-testing const trpcClient = createMockTrpcClient({ "blog.articleBySlug": { id: "1", title: "Hello", slug: "hello" }, "blog.listArticles": [], }); ``` Combine with your app's TRPCProvider for components that need a tRPC client in the render tree. ## Adding a contract suite See `docs/guides/tdd-workflow.md` §"Contract suite usage". ## Plan 9 test patterns These test obligations apply to every feature package. The examples below show the minimal shape — adapt to the feature's actual types. ### R25 — Output validation (use case) Every non-void use case must have a test that injects a mock returning malformed data and asserts the use case rejects with a `ZodError`. This proves `xOutputSchema.parse(result)` is actually called. ```typescript it("throws ZodError when repository returns malformed data (R25)", async () => { const badRepo = { getArticleBySlug: async () => ({ id: 1 }) }; // id should be string await expect(getArticleBySlugUseCase(badRepo as any)({ slug: "x" })) .rejects.toBeInstanceOf(ZodError); }); ``` Void use cases (`signOut`, `deleteMedia`) are exempt — they have no `xOutputSchema`. ### R26 — Router error mapping (tRPC) Each feature's `router.test.ts` must assert the correct `TRPCError.code` for at least one mapped domain error, using `xRouter.createCaller({})`. ```typescript it("returns NOT_FOUND when article is missing (R26)", async () => { const caller = blogRouter.createCaller({}); const error = await caller.articleBySlug({ slug: "missing" }).catch((e) => e); expect(error).toBeInstanceOf(TRPCError); expect(error.code).toBe("NOT_FOUND"); }); ``` Also assert `BAD_REQUEST` for at least one invalid-input call (exercises the `strict()` schema boundary). ### R27/R28 — Presenter shape (controller tests) When a controller's presenter reshapes the use-case output (e.g., `signInController` extracts `cookie` from `{ session, cookie }`), the controller test must assert against the **view shape**, not the use-case output shape. ```typescript // signInController: presenter returns value.cookie (a Cookie object) const result = await signInController(mockUseCase)({ username: "u", password: "p" }); expect(result.name).toBe(SESSION_COOKIE); // Cookie.name expect(result.value).toBeDefined(); // Cookie.value // NOT: expect(result.session).toBeDefined() — that's the use-case output, not the view ``` Identity presenters (`return value;`) skip this obligation — the view shape equals the use-case output shape.