Unit tests and fixtures
Test business rules without constructing a server when the rule does not need the RPC boundary. Pass explicit dependencies to the function under test and assert both the result and the side effects it is allowed to perform.
For an interceptor, @connectum/test-fixtures supplies transport-free requests, downstream functions, descriptor mocks, streams and error assertions. It is meant for authors of interceptor and descriptor-level tests; most application tests depend on @connectum/testing, which re-exports these helpers and adds a test server. These helpers do not prove that a running service has the right middleware or routes; cover that in integration tests.
Installation
npm install -D @connectum/test-fixturespnpm add -D @connectum/test-fixturesbun add -d @connectum/test-fixturesThe package shares @bufbuild/protobuf and @connectrpc/connect with your application; keep their versions inside the declared peer ranges. See peer dependency compatibility.
Exercise an interceptor
This test checks that error handling preserves an intentional Connect error and invokes the downstream function once:
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { Code } from '@connectrpc/connect';
import { createErrorHandlerInterceptor } from '@connectum/interceptors';
import { assertConnectError, createMockNextError, createMockRequest } from '@connectum/test-fixtures';
test('preserves an intentional not-found error', async () => {
const next = createMockNextError(Code.NotFound, 'page not found');
const invoke = createErrorHandlerInterceptor({ logErrors: false })(next);
await assert.rejects(() => invoke(createMockRequest()), (error) => {
assertConnectError(error, Code.NotFound, /page not found/);
return true;
});
assert.equal(next.mock.callCount(), 1);
});Use createMockNext({ message }) for a successful downstream call and createMockNextSlow(delay, options) for a delayed one. The successful mocks return a partial response with stream and message: when the interceptor reads headers, trailers or response descriptors, supply a real response instead. Use createMockStream() when the behavior under test consumes an async iterator.
Handlers that call other services
createMockContext, mockService and mockResolver belong to @connectum/testing. They let a handler exercise typed catalog dispatch against explicit mock implementations rather than starting remote services. The context needs a non-empty catalog with at least one method; missing mocks do not silently fall through to a real network service. See the catalog mocking example and the mock-context API.
Descriptor mocks follow protobuf-es
createMockDescMessage, createMockDescField, createMockDescMethod and createFakeMethod follow the rules of compiled protobuf-es descriptors, so create() and name lookups behave as they do in production:
create(mock)returns every declared field with its zero value, and each oneof group as{ case: undefined }.memberslists the standalone fields in declaration order, then the oneof groups.- A field
nameis the proto name.localNameandjsonNameare derived from it (user_idgivesuserId); alocalNamethat collides withconstructor,toString,toJSONorvalueOfgets a$suffix. The same suffix applies to methodlocalName(ToStringgivestoString$). - Field numbers that are not set continue from the highest number used so far; repeating a number throws an
Error. createMockNextandcreateMockNextSlowdeeply copy the configuredmessageon every call. Editing a nested response object changes neither the next response nor the configuration. Plain objects, arrays,Map,Set,Date,RegExp,Buffer, typed arrays andArrayBufferare copied; other nested objects (Headers,URL, class instances) and functions remain shared references.- Each field of a message made by
createMockDescMessagepoints back at the message throughparent, and the file reports the proto3 edition (file.editionisEdition.EDITION_PROTO3), sotoBinary,isFieldSet,equalsandcloneof@bufbuild/protobufaccept the mock. The link is circular:JSON.stringifyof a mock message that has fields throwsTypeError, as it does for a compiled descriptor, so serialisetypeNameand the field names instead. A field made bycreateMockDescFieldalone and a method made bycreateMockDescMethodhaveparentundefined; usecreateFakeMethod(service, name)for a method attached to a service. - A field created with
isSensitive: true, alone or inside thefieldsofcreateMockDescMessage, carries a realgoogle.protobuf.FieldOptionsmessage withdebugRedact: trueinproto.options.useSensitiveRedactionofcreateMockDescMethodis deprecated:MethodOptionshas no such property, so its{ debug_redact: true }marker exists only in the fixture.
import assert from 'node:assert/strict';
import { create } from '@bufbuild/protobuf';
import { createMockDescMessage } from '@connectum/test-fixtures';
const desc = createMockDescMessage('acme.v1.User', {
fields: [{ name: 'user_id', type: 'int32' }, { name: 'email' }],
});
assert.deepEqual(create(desc), { $typeName: 'acme.v1.User', userId: 0, email: '' });Limits: a mock oneof group has no fields (the declared fields are not attached to it), and a custom json_name option cannot be expressed. createMockRequest builds its url with the placeholder origin http://localhost; a request from the in-process transport carries https://in-memory/…, so pass url explicitly when the code under test depends on the origin.
Upgrading tests from earlier versions: Test fixture descriptors follow protobuf-es.
Fixture factories reject unsupported input and name the argument. assertConnectError also diagnoses a ConnectError from another installed Connect copy. See fixture input checks and assertion diagnostics for accepted values and errors.
Exact APIs
Use the test-fixtures API for factory options and the testing API for catalog mocks. Mock factories are also re-exported from @connectum/testing for compatibility; prefer their own package when the test needs no server dependency.