Skip to content

search testing

The helpers this package ships for your tests, by the path you import them from. How the test layers fit together is in the testing guide.

@evolve-framework/search/testing

InMemoryIndexAdapter and FakeCatalogSource, for testing an indexer or a mapper without a provider, and describeSearchIndexAdapterContract, the scenarios every index adapter meets

FakeCatalogSourceA DocumentSource over an in-memory list of entities, so the sync engine can be tested without a commerce backend.
import { FakeCatalogSource } from "@evolve-framework/search/testing";
new FakeCatalogSource(options: FakeCatalogSourceOptions)
OptionTypeDescription
scopesIndexScope[]
entitiesFakeEntity[]
fansOut?boolean

Fan each entity out into one document per SKU.

affects?Record<string, string[]>

Entities whose documents embed each other, keyed by entity id.

documentTypes?string[]

Types this source declares. Defaults to the distinct types its entities emit, which is what a real source derives from its mappers.

MethodDescription
getAffectedEntities(sourceId: string): Promise<string[]>Other entities whose documents embed data from this one, and are therefore stale once it changes.
getScopes(): Promise<IndexScope[]>Every scope this source feeds.
getDocuments( scope: IndexScope, sourceId: string, ): Promise<ProductDocument[]>Build every document for one source entity in one scope.
iterate(scope: IndexScope): AsyncIterable<ProductDocument[]>Every document for a scope, batched per source entity.
InMemoryIndexAdapterA SearchIndexAdapter that keeps documents in a Map, for testing the sync engine and any DocumentSource without a provider.
import { InMemoryIndexAdapter } from "@evolve-framework/search/testing";

Records the batch sizes it was called with, so tests can assert that chunking respects batchSize.

new InMemoryIndexAdapter(options?: InMemoryIndexAdapterOptions)
OptionTypeDescription
batchSize?number

Maximum documents per upsert call. Defaults to 100.

deleteBySource?boolean

Advertise and implement deleteBySource. Defaults to true.

fullReindex?boolean

Advertise and implement beginFullReindex. Defaults to true.

MethodDescription
init(): Promise<void>One-shot, idempotent initialization (build SDK clients, etc.).
upsertDocuments(scope: IndexScope, documents: TDoc[]): Promise<void>Store each document under its id in the scope's target for its type, replacing one already there, and record the batch size in upsertBatches.
deleteDocuments( scope: IndexScope, ids: string[], documentTypes: string[], ): Promise<void>Remove documents by id, from the target of every type in documentTypes.
documents(scope: IndexScope, documentType?: string | undefined): TDoc[]Every document currently in a scope's target.
documentIds( scope: IndexScope, documentType?: string, ): string[]The ids of InMemoryIndexAdapter.documents, in the same order.
describeSearchIndexAdapterContractThe scenarios every SearchIndexAdapter meets, run the way the framework drives an adapter: through IndexSyncService over a FakeCatalogSource.
import { describeSearchIndexAdapterContract } from "@evolve-framework/search/testing";

Scenarios a capability rules out are skipped rather than failed.

function describeSearchIndexAdapterContract(
name: string,
create: () => IndexAdapterUnderTest | Promise<IndexAdapterUnderTest>,
): void

Returns void

FakeEntity
import { type FakeEntity } from "@evolve-framework/search/testing";
export type FakeEntity = {
id: string;
name: string;
skus?: string[];
storeKeys?: string[];
documentType?: string;
};
IndexAdapterUnderTestOne adapter over a fresh, empty backend, and the means to look inside it.
import { type IndexAdapterUnderTest } from "@evolve-framework/search/testing";
export type IndexAdapterUnderTest = {
adapter: SearchIndexAdapter<ProductDocument>;
read: (scope: IndexScope, documentType: string) => Promise<string[]>;
failNextWrite?: () => void;
};