Testing and Mocking
Learn how to test oRPC procedures directly with server-side clients and create mock implementations with the implementer.
Testing
For fast, focused tests, use Server-Side Clients or call your procedures directly with call. This lets you verify validation, middleware, and handler logic without going through HTTP.
import { call } from '@orpc/server'
it('lists planets', async () => {
await expect(
call(router.planet.list, { page: 1, size: 10 })
).resolves.toEqual([
{ id: '1', name: 'Earth' },
{ id: '2', name: 'Mars' },
])
})
Mocking
Use the Implementer to create test-specific versions of a procedure or router. This is useful when one part of your system depends on another procedure, but your test should not execute the real implementation.
import { function implement<TContract extends RouterContract, TInitialContext extends Context = DefaultInitialContext>(contract: TContract, config?: ProcedureConfig): Implementer<TContract, TInitialContext & object>Turns a contract into an implementer, used to implement the contract's
procedures, routers, and middleware with full type safety.implement } from '@orpc/server'
const const fakeListPlanet: ImplementedProcedure<DefaultInitialContext & object, object, ZodObject<{
limit: ZodOptional<ZodNumber>;
cursor: ZodDefault<ZodNumber>;
}, $strip>, ZodArray<ZodObject<{
id: ZodNumber;
name: ZodString;
description: ZodOptional<ZodString>;
}, $strip>>, object>
fakeListPlanet = implement<ImplementedProcedure<{
headers?: IncomingHttpHeaders;
} & object, object, ZodObject<{
limit: ZodOptional<ZodNumber>;
cursor: ZodDefault<ZodNumber>;
}, $strip>, ZodArray<ZodObject<{
id: ZodNumber;
name: ZodString;
description: ZodOptional<ZodString>;
}, $strip>>, object>, DefaultInitialContext>(contract: ImplementedProcedure<{
headers?: IncomingHttpHeaders;
} & object, object, ZodObject<...>, ZodArray<...>, object>, config?: ProcedureConfig): Implementer<...>
Turns a contract into an implementer, used to implement the contract's
procedures, routers, and middleware with full type safety.implement(const router: {
planet: {
list: ImplementedProcedure<{
headers?: IncomingHttpHeaders;
} & object, object, ZodObject<{
limit: ZodOptional<ZodNumber>;
cursor: ZodDefault<ZodNumber>;
}, $strip>, ZodArray<ZodObject<{
id: ZodNumber;
name: ZodString;
description: ZodOptional<ZodString>;
}, $strip>>, object>;
find: ImplementedProcedure<{
headers?: IncomingHttpHeaders;
} & object, object, ZodObject<{
id: ZodNumber;
}, $strip>, ZodObject<...>, object>;
create: ImplementedProcedure<...>;
};
}
router.planet: {
list: ImplementedProcedure<{
headers?: IncomingHttpHeaders;
} & object, object, ZodObject<{
limit: ZodOptional<ZodNumber>;
cursor: ZodDefault<ZodNumber>;
}, $strip>, ZodArray<ZodObject<{
id: ZodNumber;
name: ZodString;
description: ZodOptional<ZodString>;
}, $strip>>, object>;
find: ImplementedProcedure<{
headers?: IncomingHttpHeaders;
} & object, object, ZodObject<{
id: ZodNumber;
}, $strip>, ZodObject<...>, object>;
create: ImplementedProcedure<...>;
}
planet.list: ImplementedProcedure<{
headers?: IncomingHttpHeaders;
} & object, object, ZodObject<{
limit: ZodOptional<ZodNumber>;
cursor: ZodDefault<ZodNumber>;
}, $strip>, ZodArray<ZodObject<{
id: ZodNumber;
name: ZodString;
description: ZodOptional<ZodString>;
}, $strip>>, object>
list).ProcedureImplementer<DefaultInitialContext & object, object, ZodObject<{ limit: ZodOptional<ZodNumber>; cursor: ZodDefault<ZodNumber>; }, $strip>, ZodArray<...>, object>['handler'](handler: ProcedureHandler<DefaultInitialContext & object, {
cursor: number;
limit?: number | undefined;
}, {
id: number;
name: string;
description?: string | undefined;
}[] | AnyORPCError, object>): ImplementedProcedure<DefaultInitialContext & object, object, ZodObject<{
limit: ZodOptional<ZodNumber>;
cursor: ZodDefault<ZodNumber>;
}, $strip>, ZodArray<ZodObject<{
id: ZodNumber;
name: ZodString;
description: ZodOptional<...>;
}, $strip>>, object>
handler(() => [])
Use fakeListPlanet anywhere your test would normally use the real listPlanet procedure.