From f703a59da146cabfb671089f15551233ddfa345c Mon Sep 17 00:00:00 2001 From: Homa Wong Date: Sat, 6 Jan 2018 00:14:05 -0800 Subject: [PATCH] feat: add spyAsync for Promise calls (#20) * feat: add spyAsync for Promise calls * chore: remove is-promise * chore: misc clean up * chore: fix lint Disable the lint for these lines as I want to explicity test the CallRecords --- package-lock.json | 50 +++++++++++++++++++++++------------------------ package.json | 2 +- src/spy.spec.ts | 37 ++++++++++++++++++++++++++++++++++- src/spy.ts | 48 +++++++++++++++++++++++++++++++++++++-------- tsconfig.json | 5 +++++ 5 files changed, 107 insertions(+), 35 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9ea5387..2e799cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9224,14 +9224,34 @@ "tsutils": "2.12.1" } }, + "tslint-config-standard": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tslint-config-standard/-/tslint-config-standard-7.0.0.tgz", + "integrity": "sha512-QCrLt8WwiRgZpRSgRsk6cExy8/Vipa/5fHespm4Q1ly90EB6Lni04Ub8dkEW10bV3fPN3SkxEwj41ZOe/knCZA==", + "dev": true, + "requires": { + "tslint-eslint-rules": "4.1.1" + } + }, "tslint-config-unional": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/tslint-config-unional/-/tslint-config-unional-0.8.0.tgz", - "integrity": "sha1-0t4Ip9cQV4DRenMeKzVUCX1/OwY=", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/tslint-config-unional/-/tslint-config-unional-0.9.0.tgz", + "integrity": "sha512-a3YjjfobUznkVmqj05Mn632dcY+/SIqiX2zZR9s46I+9FSEFiXw9j8tWcJAcQTf+XkDwFEffS4mV9wg2PbrV7Q==", + "dev": true, + "requires": { + "tslint-config-standard": "7.0.0", + "tslint-eslint-rules": "4.1.1" + } + }, + "tslint-eslint-rules": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tslint-eslint-rules/-/tslint-eslint-rules-4.1.1.tgz", + "integrity": "sha1-fDDniC8mvCdr/5HSOEl1xp2viLo=", "dev": true, "requires": { - "tslint-config-standard": "5.0.2", - "tslint-eslint-rules": "4.0.0" + "doctrine": "0.7.2", + "tslib": "1.8.0", + "tsutils": "1.9.1" }, "dependencies": { "doctrine": { @@ -9256,26 +9276,6 @@ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true }, - "tslint-config-standard": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/tslint-config-standard/-/tslint-config-standard-5.0.2.tgz", - "integrity": "sha1-6Y/VxBKmuXN5g2bcLIVQjPDtdA8=", - "dev": true, - "requires": { - "tslint-eslint-rules": "4.0.0" - } - }, - "tslint-eslint-rules": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tslint-eslint-rules/-/tslint-eslint-rules-4.0.0.tgz", - "integrity": "sha1-Tg5Z7NVwHJpIxm7Ue9yvscY10ns=", - "dev": true, - "requires": { - "doctrine": "0.7.2", - "tslib": "1.8.0", - "tsutils": "1.9.1" - } - }, "tsutils": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-1.9.1.tgz", diff --git a/package.json b/package.json index b8fa0a5..cabbde7 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "rimraf": "^2.6.2", "semantic-release": "^11.0.2", "tslint": "^5.8.0", - "tslint-config-unional": "^0.8.0", + "tslint-config-unional": "^0.9.0", "typescript": "^2.6.1" }, "dependencies": { diff --git a/src/spy.spec.ts b/src/spy.spec.ts index b313dd7..754a2e9 100644 --- a/src/spy.spec.ts +++ b/src/spy.spec.ts @@ -1,6 +1,6 @@ import { test } from 'ava' -import { spy } from './index' +import { spy, spyAsync } from './index' function increment(x: number) { return ++x } @@ -52,3 +52,38 @@ test('callback are spied', t => { t.is(cr.arguments[0], 1) t.is(cr.arguments[1].calls[0].arguments[0], 1) }) + +const resolve = x => Promise.resolve(x) + +test('then() will receive result from promise', t => { + const spied = spyAsync(resolve) + // tslint:disable-next-line + spied(1) + return spied.calls[0].then(x => t.is(x, 1)) +}) + +test('result from promise can be retrieved from await on the call', async t => { + const spied = spyAsync(resolve) + // tslint:disable-next-line + spied(1) + t.is(await spied.calls[0], 1) +}) + +const reject = x => Promise.reject(x) + +test('throws() will receive error thrown by promise', t => { + const spied = spyAsync(reject) + // tslint:disable-next-line + spied(1) + return spied.calls[0].throws(x => t.is(x, 1)) +}) + +test('error result is received on catch block', async t => { + const spied = spyAsync(reject) + try { + await spied(1) + } + catch (x) { + t.is(x, 1) + } +}) diff --git a/src/spy.ts b/src/spy.ts index ca876e9..460278a 100644 --- a/src/spy.ts +++ b/src/spy.ts @@ -1,21 +1,23 @@ -export interface Spy { - calls: ReadonlyArray -} - export interface CallRecord { - arguments: any[], - result?: any, - error?: Error + result: any, + error: Error +} + +export interface Spy { + calls: ReadonlyArray } +/** + * Spy on function that uses callback. + */ export function spy(fn: T): T & Spy { const calls: CallRecord[] = [] const spiedFn: T = function (...args) { const spiedArgs = args.map(a => { return typeof a === 'function' ? spy(a) : a }) - const call: CallRecord = { arguments: spiedArgs } + const call = { arguments: spiedArgs } as CallRecord calls.push(call) try { const result = fn(...spiedArgs) @@ -32,3 +34,33 @@ export function spy(fn: T): T & Spy { calls }) } + + +export interface AsyncCallRecord extends Promise { + arguments: any[], + throws(errback: any): Promise +} + +export interface AsyncSpy { + calls: ReadonlyArray +} + +/** + * Spy on function that returns a promise. + */ +export function spyAsync(fn: T): T & AsyncSpy { + const calls: AsyncCallRecord[] = [] + const spiedFn: T = function (...args) { + const call = { arguments: args } as AsyncCallRecord + calls.push(call) + const result = fn(...args) + call.then = (cb, eb) => result.then(cb, eb) + call.catch = cb => (result.catch(cb)) + call.throws = call.catch + return result + } as any + + return Object.assign(spiedFn, { + calls + }) +} diff --git a/tsconfig.json b/tsconfig.json index 0561b26..25f6a5e 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,11 @@ }, "buildOnSave": false, "compileOnSave": false, + "compilerOptions": { + "plugins": [ + { "name": "tslint-language-service"} + ] + }, "include": [ "src" ]