testing/src/mock.ts

87 lines
1.8 KiB
TypeScript

import { expect } from "./assertions.ts";
import * as _mock from "./expect/mock.ts";
export const mockfn = _mock.fn;
/**
* Patch an object's method
*/
export function patch<
O,
K extends keyof O,
F extends Function & O[K],
>(
obj: O,
method: K,
lifetime: (mock: MockManipulator<F>) => void,
): F {
const orig = obj[method] as F;
let stubs: { impl: F; loop: boolean }[] = [];
const createMock = () =>
mockfn((...args: any[]) => {
if (stubs.length) {
const result = stubs[0].impl(...args);
if (!stubs[0].loop) {
stubs.shift();
}
return result;
} else {
return orig.call(obj, ...args);
}
});
let mock = createMock();
Object.assign(obj, { [method]: mock });
try {
lifetime({
_toExpected() {
return mock;
},
stub(impl?: F, loop = false) {
if (impl) {
stubs.push({ impl, loop });
} else {
stubs.push({ impl: function () {} as any, loop: true });
}
},
reset() {
mock = createMock();
Object.assign(obj, { [method]: mock });
stubs = [];
},
});
} finally {
Object.assign(obj, { [method]: orig });
}
return mock as unknown as F;
}
/**
* Similar to *patch*, but with stub as enforced default
*/
export function mock<
O,
K extends keyof O,
F extends (...args: any) => any & O[K],
>(
obj: O,
method: K,
stub: F | ReturnType<F> | undefined,
lifetime: (mock: MockManipulator<F>) => void,
): F {
return patch(obj, method, (mock) => {
mock.stub(
typeof stub == "function" ? stub as any : (() => stub) as F,
true,
);
lifetime(mock as any);
}) as unknown as F;
}
type MockManipulator<F extends Function> = {
_toExpected: () => Parameters<typeof expect>[0];
reset: () => void;
stub: (impl?: F, loop?: boolean) => void;
};