Skip to content

Instantly share code, notes, and snippets.

@Grohden
Created September 3, 2025 17:47
Show Gist options
  • Save Grohden/e473f7541c7675225481a205f07e474d to your computer and use it in GitHub Desktop.
Save Grohden/e473f7541c7675225481a205f07e474d to your computer and use it in GitHub Desktop.
Factory bot like mock API for js
type Primitive = string | number | boolean | bigint | symbol | undefined | null;
// When the type is an array we want you to make your own createMockFn
// for the array elements
type CustomDeepPartial<T> = {
[P in keyof T]?: T[P] extends unknown[]
? T[P]
: T[P] extends Primitive
? T[P]
: CustomDeepPartial<T[P]>;
};
export const delegateMockFn =
<T>(defaults: T) =>
(overrides: CustomDeepPartial<T> = {}): T => {
const mocked: Record<string, unknown> = {};
if (overrides === null) {
// @ts-expect-error: we're only going to prove function boundaries (in->out)
return null;
}
// @ts-expect-error: we're only going to prove function boundaries (in->out)
for (const [key, value] of Object.entries(defaults)) {
if (
typeof value === 'object' &&
value !== null &&
!Array.isArray(value)
) {
mocked[key] =
key in overrides
? // @ts-ignore: we're only going to prove function boundaries (in->out)
delegateMockFn(value)(overrides[key])
: value;
} else {
// @ts-expect-error: we're only going to prove function boundaries (in->out)
mocked[key] = key in overrides ? overrides[key] : value;
}
}
return mocked as T;
};
type TraitMap<T> = Record<string, () => CustomDeepPartial<T>>;
export const createMockFn =
<T, M extends TraitMap<T> = TraitMap<T>>(
makeDefaults: () => T,
traitMap?: M
) =>
(overrides: CustomDeepPartial<T> = {}, traits: (keyof M)[] = []): T => {
const withTraits = traits.reduce(
(current, trait) => delegateMockFn(current)(traitMap![trait]()),
makeDefaults()
);
return delegateMockFn(withTraits)(overrides);
};
@Grohden
Copy link
Author

Grohden commented Sep 3, 2025

usage like:

type MyResponse = { id: string; accepted: boolean, rejectionReason: string | null } 

const mockResponse = createMockFn(
  (): MyResponse => ({ id: 'ID', accepted: true, rejectionReason: null }),
  {
     rejected: () => ({ accepted: false, rejectionReason: 'trait rejected' })
  }
)

mockResponse() //  { id: 'ID', accepted: true, rejectionReason: null }
mockResponse({}, ['rejected']) //  { id: 'ID', accepted: false, rejectionReason: 'trait rejected' }
mockResponse({ rejectionReason: 'custom' }, ['rejected']) //  { id: 'ID', accepted: false, rejectionReason: 'custom' }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment