> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tappify.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# createTapMock

> Builds the bridge a component talks to in a test, without rendering anything.

```ts theme={null}
function createTapMock(options?): TapMock;
```

Builds the bridge a component talks to in a test, without rendering anything.

## Parameters

### options?

[`CreateTapMockOptions`](/extensions/reference/testing-api/Interface.CreateTapMockOptions) = `{}`

## Returns

[`TapMock`](/extensions/reference/testing-api/Interface.TapMock)

## Remarks

Data queries answer from the sandbox fixtures, storage is an in-memory store, and
everything else the host would do is recorded in `calls`. Scopes default to all of
them: pass `scopes` to test the refused path, where a data query rejects with
`TAP_SCOPE_MISSING` for the kind's own scope and every storage call, reads
included, needs `storage:write`. A procedure needs either `server`, which answers
in process, or `handler`, which is called over a real `Request` with a token the
mock signs — so that handler has to be built with `jwks: await testJwks()` and the
same `extensionId` — and without either, `tap.server.<name>` rejects with
`TAP_UNKNOWN_PROCEDURE`. With `storage` given, a collection the map does not name
throws `TAP_UNKNOWN_COLLECTION`. Unlike the host, this mock records
`tap.actions.run` and resolves it as pending rather than refusing it, so a test
can assert on the call today. Use `renderWithTap` when there is a component to
render.

## Example

```ts theme={null}
import { createTapMock } from '@tappify/extension-sdk/testing';
import { expect, it } from 'vitest';

it('answers a keyword query from the fixtures', async () => {
  const mock = createTapMock({ scopes: ['store.metadata:read'] });
  const keywords = await mock.tap.data.query({ kind: 'keywords' });
  expect(keywords.keywords).toHaveLength(3);
});
```
