stub 函数
stub code 是占坑的代码,桩代码给出的实现是临时性的,该代码表示当前代码等待编辑。它使得程序在结构上能够符合标准,又能够使程序员可以暂时不编辑这段代码。适用于自顶向下开发或者测试时候使用。
大部分情况下,我们直接手写代码即可,但是针对不同参数还需要适配。
git 开源库 sbuts 完整的表示了该功能。
使用方式:
stub 函数生产一个值
import stub from 'sbuts';
// 调用函数时,返回固定值let fn = stub(function *(){ yield 42;});
// 或者
fn = stub(42);
// 或者
fn = stub().return(42);
// 无论传入什么参数,都返回 42fn('whatever') === 42;
stub 函数生成值列表
// 也可以返回多个数值fn = stub(function *(){ yield 42; yield 'woot';});
// 或者
fn = stub(42,'woot');
// 或者
fn = stub() .return(42) .return('woot'); // can be called later
// 奇数次调用返回 42,偶数次调用返回 woot 字符串fn('foo') === 42; // > truefn(66) === 'woot'; // > true
stub 函数异步产生数值
fn = stub(async function *(){ yield 42;});
// or
fn = stub(function * (){ yield Promise.resolve(42);});
// or
fn = stub(Promise.resolve(42));
// or
fn = stub().resolve(42);
stub 函数产生错误
// 同步抛出错误fn = stub(function *(){ throw new Error('some error');});
// 或者
fn = stub().throw(new Error('some error'));
// 异步抛出错误fn = stub(async function *(){ throw new Error('some error');});
fn = stub(function * (){ yield Promise.reject(new Error('some error'));});
fn = stub(Promise.reject(new Error('some message')));
fn = stub().reject(new Error('some message'));
源码解析
const STUB_EXHAUSTED_ERROR = `stub exhausted, call not expected`;
interface CallList<T> { calls: T[], callCount: number; called: boolean;}
type StubbedFn = (...args: unknown[]) => unknown;type Stub<T extends StubbedFn> = T & CallList<Parameters<T>>type LazyExtension<T extends StubbedFn> = { return<K = ReturnType<T>>(value: K): LazyStub<T>; resolve<K>(value: K): LazyStub<T>; reject<K extends any = Error>(reason: K): LazyStub<T>; throw<K extends any = Error>(val: K): LazyStub<T>;};type LazyStub<T extends StubbedFn> = Stub<T> & LazyExtension<T>
const descriptor = (calls: unknown[]) => ({ calls: { value: calls }, callCount: { get() { return calls.length; } }, called: { get() { return calls.length > 0; } }});
const fromAsyncGenerator = <T extends (...args: unknown[]) => Promise<unknown>>(generator: AsyncGenerator): Stub<T> => { const calls: Parameters<T>[] = []; return Object.defineProperties(fn, descriptor(calls));
async function fn(...args: Parameters<T>) { calls.push(args); const {value, done} = await generator.next(); if (done) { throw new Error(STUB_EXHAUSTED_ERROR); } return value; }};
const fromGenerator = <T extends StubbedFn>(generator: Generator): Stub<T> => { const calls: Parameters<T>[] = []; return Object.defineProperties(fn, descriptor(calls));
function fn(...args: Parameters<T>): ReturnType<T> { calls.push(args); const {value, done} = generator.next(); if (done) { throw new Error(STUB_EXHAUSTED_ERROR); } return value; }};
enum CallType { ERROR = 'error', VALUE = 'value'}
interface CallDefinition<T> { type: CallType, value: T}
const fromVoid = <T extends StubbedFn>(): LazyStub<T> => { const queue: CallDefinition<any>[] = []; const generator = (function* () { while (true) { const next = queue.shift(); if (!next) { break; } const {type, value} = next; if (type === CallType.ERROR) { throw value; } yield value; } })();
const fn = fromGenerator<T>(generator); const queueCall = <K>(value: K | Error, type = CallType.VALUE) => { queue.push({value, type}); return fn as LazyStub<T>; };
const extension: LazyExtension<T> = { return: queueCall, resolve: <K>(val: K) => queueCall<Promise<K>>(Promise.resolve(val)), reject: <K extends any = Error>(val: K) => queueCall<Promise<K>>(Promise.reject(val)), throw: <K extends any = Error>(val: K) => queueCall<K>(val, CallType.ERROR) };
return Object.assign(fn, extension);};
function stub<T extends StubbedFn>(generator: Generator): Stub<T>;function stub<T extends StubbedFn>(generator: AsyncGenerator): Stub<T>;function stub<T extends StubbedFn>(): LazyStub<T>;function stub<T extends StubbedFn>(...args:unknown[]): Stub<T>;function stub<T extends (...arg: unknown[]) => unknown>(...args: unknown[]) { const [input] = args;
if (typeof input === 'function') { const generatorObject = input(); return generatorObject[Symbol.asyncIterator] ? fromAsyncGenerator(generatorObject) : fromGenerator(generatorObject); }
return args.length === 0 ? fromVoid() : fromGenerator(function* () { yield* args; }());}
export default stub