Wraps an instance method with a Spy.
(self: Self, property: keyof Self)
| 608 | |
| 609 | /** Wraps an instance method with a Spy. */ |
| 610 | function methodSpy< |
| 611 | Self, |
| 612 | Args extends unknown[], |
| 613 | Return, |
| 614 | >(self: Self, property: keyof Self): MethodSpy<Self, Args, Return> { |
| 615 | if (typeof self[property] !== "function") { |
| 616 | throw new MockError( |
| 617 | "Cannot spy: property is not an instance method", |
| 618 | ); |
| 619 | } |
| 620 | if (isSpy(self[property])) { |
| 621 | throw new MockError( |
| 622 | "Cannot spy: already spying on instance method", |
| 623 | ); |
| 624 | } |
| 625 | |
| 626 | const propertyDescriptor = Object.getOwnPropertyDescriptor(self, property); |
| 627 | if (propertyDescriptor && !propertyDescriptor.configurable) { |
| 628 | throw new MockError( |
| 629 | "Cannot spy: non-configurable instance method", |
| 630 | ); |
| 631 | } |
| 632 | |
| 633 | const original = self[property] as unknown as ( |
| 634 | this: Self, |
| 635 | ...args: Args |
| 636 | ) => Return; |
| 637 | const calls: SpyCall<Self, Args, Return>[] = []; |
| 638 | let restored = false; |
| 639 | const spy = function (this: Self, ...args: Args): Return { |
| 640 | const call: SpyCall<Self, Args, Return> = { args }; |
| 641 | if (this) call.self = this; |
| 642 | try { |
| 643 | call.returned = original.apply(this, args); |
| 644 | } catch (error) { |
| 645 | call.error = error as Error; |
| 646 | calls.push(call); |
| 647 | throw error; |
| 648 | } |
| 649 | calls.push(call); |
| 650 | return call.returned; |
| 651 | } as MethodSpy<Self, Args, Return>; |
| 652 | Object.defineProperties(spy, { |
| 653 | original: { |
| 654 | enumerable: true, |
| 655 | value: original, |
| 656 | }, |
| 657 | calls: { |
| 658 | enumerable: true, |
| 659 | value: calls, |
| 660 | }, |
| 661 | restored: { |
| 662 | enumerable: true, |
| 663 | get: () => restored, |
| 664 | }, |
| 665 | restore: { |
| 666 | enumerable: true, |
| 667 | value: () => { |
no test coverage detected