Testing
Exercise your integration with the in-memory provider from repo-sdk/testing.
repo-sdk/testing ships createInMemoryProvider, an in-memory RepoProvider you can drive with
createClient to test your integration without hitting a real provider. It uses a small page size, so
cursor pagination is exercised too, and it accepts an optional seed of namespaces, repos, commits,
branches, tags, and webhooks.
Basic usage
Create the provider, hand it to createClient, and drive it exactly like a real client:
import { createClient } from 'repo-sdk';
import { createInMemoryProvider } from 'repo-sdk/testing';
const provider = createInMemoryProvider({
namespaces: [{ id: '1', slug: 'acme', name: 'Acme', kind: 'organization' }],
repositories: {
'acme/app': { name: 'app', namespace: 'acme', defaultBranch: 'main' },
},
commits: {
'acme/app': [{ sha: 'abc123', message: 'Initial commit' }],
},
});
const client = createClient({ provider });
const { data } = await client.repos.list({ namespace: 'acme' });
The seed is optional — createInMemoryProvider() starts empty.
Simulating a provider and its capabilities
By default the in-memory provider reports itself as github with every capability enabled. A second
options argument lets you simulate a specific provider and its capability gaps, so code that branches
on client.capabilities before offering a feature is testable:
const provider = createInMemoryProvider({}, {
name: 'azure-devops',
capabilities: { repoSearch: false },
});
const client = createClient({ provider });
client.providerName; // 'azure-devops'
await client.repos.list({ query: 'app' }); // throws RepoError with code 'unsupported'
capabilities is a Partial<RepoCapabilities> merged over the full-featured defaults — override only
what your test needs. Check the capability matrix for what each
real provider supports.
The seed shape
namespaces is an array; repositories, commits, branches, tags, and webhooks are
records keyed by repository path (e.g. acme/app).
user?InMemoryUserSeed
The authenticated user returned by users.me. Defaults to id user-1 / username in-memory-user.
InMemoryUserSeednamespaces?InMemoryNamespaceSeed[]
Namespaces, as an array of { id, slug, name, kind, parent? }.
InMemoryNamespaceSeed[]repositories?Record<string, InMemoryRepositorySeed>
Repositories keyed by path. Fields default sensibly (e.g. defaultBranch → main).
Record<string, InMemoryRepositorySeed>commits?Record<string, InMemoryCommitSeed[]>
Commits keyed by repo path, newest first.
Record<string, InMemoryCommitSeed[]>branches?Record<string, InMemoryBranchSeed[]>
Branches keyed by repo path, as { name, sha }. Drives branches.list and refs.search.
Record<string, InMemoryBranchSeed[]>tags?Record<string, InMemoryTagSeed[]>
Tags keyed by repo path.
Record<string, InMemoryTagSeed[]>webhooks?Record<string, InMemoryWebhookSeed[]>
Webhooks keyed by repo path.
Record<string, InMemoryWebhookSeed[]>A fuller seed:
const provider = createInMemoryProvider({
namespaces: [{ id: '1', slug: 'acme', name: 'Acme', kind: 'organization' }],
repositories: {
'acme/app': { name: 'app', namespace: 'acme', defaultBranch: 'main', owned: true },
},
commits: {
'acme/app': [
{ sha: 'c2', message: 'Second', refs: ['main'] },
{ sha: 'c1', message: 'First', refs: ['main', 'v1.0.0'] },
],
},
branches: {
'acme/app': [{ name: 'main', sha: 'c2' }],
},
tags: {
'acme/app': [{ name: 'v1.0.0', sha: 'c1', isAnnotated: true, date: new Date('2024-01-01') }],
},
webhooks: {
'acme/app': [{ url: 'https://example.com/hook', events: ['push'] }],
},
});
Asserting against state
The provider exposes its data on provider.state as Maps (plus a namespaces array), so you can
assert on the effects of your code — for example, that a webhook was created:
import { expect, test } from 'vitest';
test('registers a webhook', async () => {
const provider = createInMemoryProvider({
repositories: { 'acme/app': { name: 'app', namespace: 'acme' } },
});
const client = createClient({ provider });
await client.webhooks.create({
repo: 'acme/app',
url: 'https://example.com/hook',
events: ['push'],
});
expect(provider.state.webhooks.get('acme/app')).toHaveLength(1);
});
provider.state is { namespaces: Namespace[], repositories: Map, commits: Map, branches: Map, tags: Map, webhooks: Map }.
Pagination is exercised
The in-memory provider uses a deliberately small page size of 2, so a seed with three or more items
returns a cursor and your cursor-following code (list with cursor, or listAll) is exercised
rather than always fitting in one page.
const provider = createInMemoryProvider({
namespaces: [
{ id: '1', slug: 'a', name: 'A', kind: 'organization' },
{ id: '2', slug: 'b', name: 'B', kind: 'organization' },
{ id: '3', slug: 'c', name: 'C', kind: 'organization' },
],
});
const client = createClient({ provider });
const first = await client.namespaces.list();
first.data.length; // 2
first.cursor; // defined — there's a third item
let count = 0;
for await (const _ of client.namespaces.listAll()) count++;
count; // 3
Reference: Provider support
Compare the real providers feature by feature.