From e044e9ed0723bb2b6b1816fd28d6707be787b138 Mon Sep 17 00:00:00 2001 From: Dmitry Semigradsky Date: Sat, 11 Feb 2023 13:28:50 +0300 Subject: [PATCH] [node] Add mocking --- types/node/test.d.ts | 239 +++++++++++++++++++++++++++- types/node/test/test.ts | 284 +++++++++++++++++++++++++++++++++ types/node/ts4.8/test.d.ts | 248 ++++++++++++++++++++++++++++- types/node/ts4.8/test/test.ts | 286 ++++++++++++++++++++++++++++++++++ 4 files changed, 1055 insertions(+), 2 deletions(-) diff --git a/types/node/test.d.ts b/types/node/test.d.ts index 58b8c45364e65b..8c76d247765e8f 100644 --- a/types/node/test.d.ts +++ b/types/node/test.d.ts @@ -342,6 +342,10 @@ declare module 'node:test' { * @returns A {@link Promise} resolved with `undefined` once the test completes. */ test: typeof test; + /** + * Each test provides its own MockTracker instance. + */ + readonly mock: MockTracker; } interface TestOptions { @@ -451,5 +455,238 @@ declare module 'node:test' { timeout?: number | undefined; } - export { test as default, run, test, describe, it, before, after, beforeEach, afterEach }; + interface MockFunctionOptions { + /** + * The number of times that the mock will use the behavior of `implementation`. + * Once the mock function has been called `times` times, + * it will automatically restore the behavior of `original`. + * This value must be an integer greater than zero. + * @default Infinity + */ + times?: number | undefined; + } + + interface MockMethodOptions extends MockFunctionOptions { + /** + * If `true`, `object[methodName]` is treated as a getter. + * This option cannot be used with the `setter` option. + */ + getter?: boolean | undefined; + + /** + * If `true`, `object[methodName]` is treated as a setter. + * This option cannot be used with the `getter` option. + */ + setter?: boolean | undefined; + } + + type Mock = F & { + mock: MockFunctionContext; + }; + + type NoOpFunction = (...args: any[]) => undefined; + + type FunctionPropertyNames = { + [K in keyof T]: T[K] extends Function ? K : never; + }[keyof T]; + + interface MockTracker { + /** + * This function is used to create a mock function. + * @param original An optional function to create a mock on. + * @param implementation An optional function used as the mock implementation for `original`. + * This is useful for creating mocks that exhibit one behavior for a specified number of calls and then restore the behavior of `original`. + * @param options Optional configuration options for the mock function. + */ + fn(original?: F, options?: MockFunctionOptions): Mock; + fn(original?: F, implementation?: Implementation, options?: MockFunctionOptions): Mock; + + /** + * This function is used to create a mock on an existing object method. + * @param object The object whose method is being mocked. + * @param methodName The identifier of the method on `object` to mock. If `object[methodName]` is not a function, an error is thrown. + * @param implementation An optional function used as the mock implementation for `object[methodName]`. + * @param options Optional configuration options for the mock method. + */ + method< + MockedObject extends object, + MethodName extends FunctionPropertyNames, + >( + object: MockedObject, + methodName: MethodName, + options?: MockFunctionOptions, + ): MockedObject[MethodName] extends Function + ? Mock + : never; + method< + MockedObject extends object, + MethodName extends FunctionPropertyNames, + Implementation extends Function, + >( + object: MockedObject, + methodName: MethodName, + implementation: Implementation, + options?: MockFunctionOptions, + ): MockedObject[MethodName] extends Function + ? Mock + : never; + method( + object: MockedObject, + methodName: keyof MockedObject, + options: MockMethodOptions, + ): Mock; + method( + object: MockedObject, + methodName: keyof MockedObject, + implementation: Function, + options: MockMethodOptions, + ): Mock; + + /** + * This function is syntax sugar for {@link MockTracker.method} with `options.getter` set to `true`. + */ + getter< + MockedObject extends object, + MethodName extends keyof MockedObject, + >( + object: MockedObject, + methodName: MethodName, + options?: MockFunctionOptions, + ): Mock<() => MockedObject[MethodName]>; + getter< + MockedObject extends object, + MethodName extends keyof MockedObject, + Implementation extends Function, + >( + object: MockedObject, + methodName: MethodName, + implementation?: Implementation, + options?: MockFunctionOptions, + ): Mock<(() => MockedObject[MethodName]) | Implementation>; + + /** + * This function is syntax sugar for {@link MockTracker.method} with `options.setter` set to `true`. + */ + setter< + MockedObject extends object, + MethodName extends keyof MockedObject, + >( + object: MockedObject, + methodName: MethodName, + options?: MockFunctionOptions, + ): Mock<(value: MockedObject[MethodName]) => void>; + setter< + MockedObject extends object, + MethodName extends keyof MockedObject, + Implementation extends Function, + >( + object: MockedObject, + methodName: MethodName, + implementation?: Implementation, + options?: MockFunctionOptions, + ): Mock<((value: MockedObject[MethodName]) => void) | Implementation>; + + /** + * This function restores the default behavior of all mocks that were previously created by this `MockTracker` + * and disassociates the mocks from the `MockTracker` instance. Once disassociated, the mocks can still be used, + * but the `MockTracker` instance can no longer be used to reset their behavior or otherwise interact with them. + * + * After each test completes, this function is called on the test context's `MockTracker`. + * If the global `MockTracker` is used extensively, calling this function manually is recommended. + */ + reset(): void; + + /** + * This function restores the default behavior of all mocks that were previously created by this `MockTracker`. + * Unlike `mock.reset()`, `mock.restoreAll()` does not disassociate the mocks from the `MockTracker` instance. + */ + restoreAll(): void; + } + + const mock: MockTracker; + + interface MockFunctionCall< + F extends Function, + ReturnType = F extends (...args: any) => infer T + ? T + : F extends abstract new (...args: any) => infer T + ? T + : unknown, + Args = F extends (...args: infer Y) => any + ? Y + : F extends abstract new (...args: infer Y) => any + ? Y + : unknown[], + > { + /** + * An array of the arguments passed to the mock function. + */ + arguments: Args; + /** + * If the mocked function threw then this property contains the thrown value. + */ + error: unknown | undefined; + /** + * The value returned by the mocked function. + * + * If the mocked function threw, it will be `undefined`. + */ + result: ReturnType | undefined; + /** + * An `Error` object whose stack can be used to determine the callsite of the mocked function invocation. + */ + stack: Error; + /** + * If the mocked function is a constructor, this field contains the class being constructed. + * Otherwise this will be `undefined`. + */ + target: F extends abstract new (...args: any) => any ? F : undefined; + /** + * The mocked function's `this` value. + */ + this: unknown; + } + + interface MockFunctionContext { + /** + * A getter that returns a copy of the internal array used to track calls to the mock. + */ + readonly calls: Array>; + + /** + * This function returns the number of times that this mock has been invoked. + * This function is more efficient than checking `ctx.calls.length` + * because `ctx.calls` is a getter that creates a copy of the internal call tracking array. + */ + callCount(): number; + + /** + * This function is used to change the behavior of an existing mock. + * @param implementation The function to be used as the mock's new implementation. + */ + mockImplementation(implementation: Function): void; + + /** + * This function is used to change the behavior of an existing mock for a single invocation. + * Once invocation `onCall` has occurred, the mock will revert to whatever behavior + * it would have used had `mockImplementationOnce()` not been called. + * @param implementation The function to be used as the mock's implementation for the invocation number specified by `onCall`. + * @param onCall The invocation number that will use `implementation`. + * If the specified invocation has already occurred then an exception is thrown. + */ + mockImplementationOnce(implementation: Function, onCall?: number): void; + + /** + * Resets the call history of the mock function. + */ + resetCalls(): void; + + /** + * Resets the implementation of the mock function to its original behavior. + * The mock can still be used after calling this function. + */ + restore(): void; + } + + export { test as default, run, test, describe, it, before, after, beforeEach, afterEach, mock }; } diff --git a/types/node/test/test.ts b/types/node/test/test.ts index 73f53836310d5f..8b4cdd88820ba9 100644 --- a/types/node/test/test.ts +++ b/types/node/test/test.ts @@ -219,3 +219,287 @@ before(() => {}, { signal: new AbortController().signal, timeout: Infinity }); beforeEach(() => {}, { signal: new AbortController().signal, timeout: Infinity }); after(() => {}, { signal: new AbortController().signal, timeout: Infinity }); beforeEach(() => {}, { signal: new AbortController().signal, timeout: Infinity }); + +test('mocks a counting function', (t) => { + let cnt = 0; + + function addOne() { + cnt++; + return cnt; + } + + function addTwo() { + cnt += 2; + return cnt; + } + + const fn = t.mock.fn(addOne, addTwo, { times: 2 }); + // $ExpectType number + fn(); +}); + +test('spies on an object method', (t) => { + const number = { + value: 5, + subtract(a: number) { + return this.value - a; + }, + }; + + const subtract = t.mock.method(number, 'subtract'); + + number.subtract(3); + + const call = subtract.mock.calls[0]; + // $ExpectType [a: number] + call.arguments; + // $ExpectType number | undefined + call.result; + // $ExpectType unknown + call.error; + // $ExpectType undefined + call.target; + + // @ts-expect-error + t.mock.method(obj, 'value'); +}); + +test('mocks an object method', (t) => { + const obj = { + prop: 5, + method(a: number, b: number) { + return a + b + this.prop; + }, + }; + + function mockMethod(this: typeof obj, a: number) { + return a + this.prop; + } + + const mocked = t.mock.method(obj, 'method', mockMethod); + obj.method(1, 3); + const call = mocked.mock.calls[0]; + + // $ExpectType [a: number, b: number] | [a: number] + call.arguments; + // $ExpectType number | undefined + call.result; + // $ExpectType unknown + call.error; + // $ExpectType undefined + call.target; + + // @ts-expect-error + t.mock.method(obj, 'prop', mockMethod); +}); + +test('a no-op spy function is created by default', (t) => { + const fn = t.mock.fn(); + fn(3, 4); + + const call = fn.mock.calls[0]; + // $ExpectType any[] + call.arguments; + // $ExpectType undefined + call.result; +}); + +test('spies on a constructor', (t) => { + class ParentClazz { + constructor(public c: number) { + this.c = c; + } + } + + class Clazz extends ParentClazz { + #privateValue; + + constructor(public a: number, b: number) { + super(a + b); + this.a = a; + this.#privateValue = b; + } + + getPrivateValue() { + return this.#privateValue; + } + } + + const ctor = t.mock.fn(Clazz); + const instance = new ctor(42, 85); + + // $ExpectType number + instance.a; + // $ExpectType number + instance.getPrivateValue(); + // $ExpectType number + instance.c; + + const call = ctor.mock.calls[0]; + // $ExpectType [a: number, b: number] + call.arguments; + // $ExpectType Clazz | undefined + call.result; + // $ExpectType typeof Clazz + call.target; +}); + +test('spies on a getter', (t) => { + const obj = { + prop: 5, + get method() { + return this.prop; + }, + }; + + { + const getter = t.mock.method(obj, 'method', { getter: true }); + console.log(obj.method); + const call = getter.mock.calls[0]; + + // $ExpectType unknown[] + call.arguments; + // $ExpectType unknown + call.result; + // $ExpectType undefined + call.target; + // $ExpectType unknown + call.this; + } + { + const getter = t.mock.getter(obj, 'method'); + console.log(obj.method); + const call = getter.mock.calls[0]; + + // $ExpectType [] + call.arguments; + // $ExpectType number | undefined + call.result; + // $ExpectType undefined + call.target; + // $ExpectType unknown + call.this; + } +}); + +test('mocks a getter', (t) => { + const obj = { + prop: 5, + get method() { + return this.prop; + }, + }; + + function mockMethod(this: typeof obj) { + return this.prop - 1; + } + + { + const getter = t.mock.method(obj, 'method', mockMethod, { getter: true }); + console.log(obj.method); + const call = getter.mock.calls[0]; + + // $ExpectType unknown[] + call.arguments; + // $ExpectType unknown + call.result; + // $ExpectType undefined + call.target; + // $ExpectType unknown + call.this; + } + { + const getter = t.mock.getter(obj, 'method', mockMethod); + console.log(obj.method); + const call = getter.mock.calls[0]; + + // $ExpectType [] + call.arguments; + // $ExpectType number | undefined + call.result; + // $ExpectType undefined + call.target; + // $ExpectType unknown + call.this; + } +}); + +test('spies on a setter', (t) => { + const obj = { + prop: 100, + set method(val: number) { + this.prop = val; + }, + }; + + { + const setter = t.mock.method(obj, 'method', { setter: true }); + obj.method = 77; + const call = setter.mock.calls[0]; + + // $ExpectType unknown[] + call.arguments; + // $ExpectType unknown + call.result; + // $ExpectType undefined + call.target; + // $ExpectType unknown + call.this; + } + { + const setter = t.mock.setter(obj, 'method'); + obj.method = 77; + const call = setter.mock.calls[0]; + + // $ExpectType [value: number] + call.arguments; + // $ExpectType void | undefined + call.result; + // $ExpectType undefined + call.target; + // $ExpectType unknown + call.this; + } +}); + +test('mocks a setter', (t) => { + const obj = { + prop: 100, + set method(val: number) { + this.prop = val; + }, + }; + + function mockMethod(this: typeof obj, value: number) { + this.prop = -value; + } + + { + const setter = t.mock.method(obj, 'method', mockMethod, { setter: true }); + obj.method = 77; + const call = setter.mock.calls[0]; + + // $ExpectType unknown[] + call.arguments; + // $ExpectType unknown + call.result; + // $ExpectType undefined + call.target; + // $ExpectType unknown + call.this; + } + { + const setter = t.mock.setter(obj, 'method', mockMethod); + obj.method = 77; + const call = setter.mock.calls[0]; + + // $ExpectType [value: number] | [value: number] + call.arguments; + // $ExpectType void | undefined + call.result; + // $ExpectType undefined + call.target; + // $ExpectType unknown + call.this; + } +}); diff --git a/types/node/ts4.8/test.d.ts b/types/node/ts4.8/test.d.ts index 8e20710eca26b8..8c76d247765e8f 100644 --- a/types/node/ts4.8/test.d.ts +++ b/types/node/ts4.8/test.d.ts @@ -264,6 +264,15 @@ declare module 'node:test' { */ beforeEach: typeof beforeEach; + /** + * This function is used to create a hook that runs after the current test finishes. + * @param fn The hook function. If the hook uses callbacks, the callback function is passed as + * the second argument. Default: A no-op function. + * @param options Configuration options for the hook. + * @since v18.13.0 + */ + after: typeof after; + /** * This function is used to create a hook running after each subtest of the current test. * @param fn The hook function. If the hook uses callbacks, the callback function is passed as @@ -333,6 +342,10 @@ declare module 'node:test' { * @returns A {@link Promise} resolved with `undefined` once the test completes. */ test: typeof test; + /** + * Each test provides its own MockTracker instance. + */ + readonly mock: MockTracker; } interface TestOptions { @@ -442,5 +455,238 @@ declare module 'node:test' { timeout?: number | undefined; } - export { test as default, run, test, describe, it, before, after, beforeEach, afterEach }; + interface MockFunctionOptions { + /** + * The number of times that the mock will use the behavior of `implementation`. + * Once the mock function has been called `times` times, + * it will automatically restore the behavior of `original`. + * This value must be an integer greater than zero. + * @default Infinity + */ + times?: number | undefined; + } + + interface MockMethodOptions extends MockFunctionOptions { + /** + * If `true`, `object[methodName]` is treated as a getter. + * This option cannot be used with the `setter` option. + */ + getter?: boolean | undefined; + + /** + * If `true`, `object[methodName]` is treated as a setter. + * This option cannot be used with the `getter` option. + */ + setter?: boolean | undefined; + } + + type Mock = F & { + mock: MockFunctionContext; + }; + + type NoOpFunction = (...args: any[]) => undefined; + + type FunctionPropertyNames = { + [K in keyof T]: T[K] extends Function ? K : never; + }[keyof T]; + + interface MockTracker { + /** + * This function is used to create a mock function. + * @param original An optional function to create a mock on. + * @param implementation An optional function used as the mock implementation for `original`. + * This is useful for creating mocks that exhibit one behavior for a specified number of calls and then restore the behavior of `original`. + * @param options Optional configuration options for the mock function. + */ + fn(original?: F, options?: MockFunctionOptions): Mock; + fn(original?: F, implementation?: Implementation, options?: MockFunctionOptions): Mock; + + /** + * This function is used to create a mock on an existing object method. + * @param object The object whose method is being mocked. + * @param methodName The identifier of the method on `object` to mock. If `object[methodName]` is not a function, an error is thrown. + * @param implementation An optional function used as the mock implementation for `object[methodName]`. + * @param options Optional configuration options for the mock method. + */ + method< + MockedObject extends object, + MethodName extends FunctionPropertyNames, + >( + object: MockedObject, + methodName: MethodName, + options?: MockFunctionOptions, + ): MockedObject[MethodName] extends Function + ? Mock + : never; + method< + MockedObject extends object, + MethodName extends FunctionPropertyNames, + Implementation extends Function, + >( + object: MockedObject, + methodName: MethodName, + implementation: Implementation, + options?: MockFunctionOptions, + ): MockedObject[MethodName] extends Function + ? Mock + : never; + method( + object: MockedObject, + methodName: keyof MockedObject, + options: MockMethodOptions, + ): Mock; + method( + object: MockedObject, + methodName: keyof MockedObject, + implementation: Function, + options: MockMethodOptions, + ): Mock; + + /** + * This function is syntax sugar for {@link MockTracker.method} with `options.getter` set to `true`. + */ + getter< + MockedObject extends object, + MethodName extends keyof MockedObject, + >( + object: MockedObject, + methodName: MethodName, + options?: MockFunctionOptions, + ): Mock<() => MockedObject[MethodName]>; + getter< + MockedObject extends object, + MethodName extends keyof MockedObject, + Implementation extends Function, + >( + object: MockedObject, + methodName: MethodName, + implementation?: Implementation, + options?: MockFunctionOptions, + ): Mock<(() => MockedObject[MethodName]) | Implementation>; + + /** + * This function is syntax sugar for {@link MockTracker.method} with `options.setter` set to `true`. + */ + setter< + MockedObject extends object, + MethodName extends keyof MockedObject, + >( + object: MockedObject, + methodName: MethodName, + options?: MockFunctionOptions, + ): Mock<(value: MockedObject[MethodName]) => void>; + setter< + MockedObject extends object, + MethodName extends keyof MockedObject, + Implementation extends Function, + >( + object: MockedObject, + methodName: MethodName, + implementation?: Implementation, + options?: MockFunctionOptions, + ): Mock<((value: MockedObject[MethodName]) => void) | Implementation>; + + /** + * This function restores the default behavior of all mocks that were previously created by this `MockTracker` + * and disassociates the mocks from the `MockTracker` instance. Once disassociated, the mocks can still be used, + * but the `MockTracker` instance can no longer be used to reset their behavior or otherwise interact with them. + * + * After each test completes, this function is called on the test context's `MockTracker`. + * If the global `MockTracker` is used extensively, calling this function manually is recommended. + */ + reset(): void; + + /** + * This function restores the default behavior of all mocks that were previously created by this `MockTracker`. + * Unlike `mock.reset()`, `mock.restoreAll()` does not disassociate the mocks from the `MockTracker` instance. + */ + restoreAll(): void; + } + + const mock: MockTracker; + + interface MockFunctionCall< + F extends Function, + ReturnType = F extends (...args: any) => infer T + ? T + : F extends abstract new (...args: any) => infer T + ? T + : unknown, + Args = F extends (...args: infer Y) => any + ? Y + : F extends abstract new (...args: infer Y) => any + ? Y + : unknown[], + > { + /** + * An array of the arguments passed to the mock function. + */ + arguments: Args; + /** + * If the mocked function threw then this property contains the thrown value. + */ + error: unknown | undefined; + /** + * The value returned by the mocked function. + * + * If the mocked function threw, it will be `undefined`. + */ + result: ReturnType | undefined; + /** + * An `Error` object whose stack can be used to determine the callsite of the mocked function invocation. + */ + stack: Error; + /** + * If the mocked function is a constructor, this field contains the class being constructed. + * Otherwise this will be `undefined`. + */ + target: F extends abstract new (...args: any) => any ? F : undefined; + /** + * The mocked function's `this` value. + */ + this: unknown; + } + + interface MockFunctionContext { + /** + * A getter that returns a copy of the internal array used to track calls to the mock. + */ + readonly calls: Array>; + + /** + * This function returns the number of times that this mock has been invoked. + * This function is more efficient than checking `ctx.calls.length` + * because `ctx.calls` is a getter that creates a copy of the internal call tracking array. + */ + callCount(): number; + + /** + * This function is used to change the behavior of an existing mock. + * @param implementation The function to be used as the mock's new implementation. + */ + mockImplementation(implementation: Function): void; + + /** + * This function is used to change the behavior of an existing mock for a single invocation. + * Once invocation `onCall` has occurred, the mock will revert to whatever behavior + * it would have used had `mockImplementationOnce()` not been called. + * @param implementation The function to be used as the mock's implementation for the invocation number specified by `onCall`. + * @param onCall The invocation number that will use `implementation`. + * If the specified invocation has already occurred then an exception is thrown. + */ + mockImplementationOnce(implementation: Function, onCall?: number): void; + + /** + * Resets the call history of the mock function. + */ + resetCalls(): void; + + /** + * Resets the implementation of the mock function to its original behavior. + * The mock can still be used after calling this function. + */ + restore(): void; + } + + export { test as default, run, test, describe, it, before, after, beforeEach, afterEach, mock }; } diff --git a/types/node/ts4.8/test/test.ts b/types/node/ts4.8/test/test.ts index 4047c0be2f030b..8b4cdd88820ba9 100644 --- a/types/node/ts4.8/test/test.ts +++ b/types/node/ts4.8/test/test.ts @@ -73,6 +73,8 @@ test(undefined, undefined, t => { // $ExpectType void t.todo(); // $ExpectType void + t.after(() => {}); + // $ExpectType void t.afterEach(() => {}); // $ExpectType void t.beforeEach(() => {}); @@ -217,3 +219,287 @@ before(() => {}, { signal: new AbortController().signal, timeout: Infinity }); beforeEach(() => {}, { signal: new AbortController().signal, timeout: Infinity }); after(() => {}, { signal: new AbortController().signal, timeout: Infinity }); beforeEach(() => {}, { signal: new AbortController().signal, timeout: Infinity }); + +test('mocks a counting function', (t) => { + let cnt = 0; + + function addOne() { + cnt++; + return cnt; + } + + function addTwo() { + cnt += 2; + return cnt; + } + + const fn = t.mock.fn(addOne, addTwo, { times: 2 }); + // $ExpectType number + fn(); +}); + +test('spies on an object method', (t) => { + const number = { + value: 5, + subtract(a: number) { + return this.value - a; + }, + }; + + const subtract = t.mock.method(number, 'subtract'); + + number.subtract(3); + + const call = subtract.mock.calls[0]; + // $ExpectType [a: number] + call.arguments; + // $ExpectType number | undefined + call.result; + // $ExpectType unknown + call.error; + // $ExpectType undefined + call.target; + + // @ts-expect-error + t.mock.method(obj, 'value'); +}); + +test('mocks an object method', (t) => { + const obj = { + prop: 5, + method(a: number, b: number) { + return a + b + this.prop; + }, + }; + + function mockMethod(this: typeof obj, a: number) { + return a + this.prop; + } + + const mocked = t.mock.method(obj, 'method', mockMethod); + obj.method(1, 3); + const call = mocked.mock.calls[0]; + + // $ExpectType [a: number, b: number] | [a: number] + call.arguments; + // $ExpectType number | undefined + call.result; + // $ExpectType unknown + call.error; + // $ExpectType undefined + call.target; + + // @ts-expect-error + t.mock.method(obj, 'prop', mockMethod); +}); + +test('a no-op spy function is created by default', (t) => { + const fn = t.mock.fn(); + fn(3, 4); + + const call = fn.mock.calls[0]; + // $ExpectType any[] + call.arguments; + // $ExpectType undefined + call.result; +}); + +test('spies on a constructor', (t) => { + class ParentClazz { + constructor(public c: number) { + this.c = c; + } + } + + class Clazz extends ParentClazz { + #privateValue; + + constructor(public a: number, b: number) { + super(a + b); + this.a = a; + this.#privateValue = b; + } + + getPrivateValue() { + return this.#privateValue; + } + } + + const ctor = t.mock.fn(Clazz); + const instance = new ctor(42, 85); + + // $ExpectType number + instance.a; + // $ExpectType number + instance.getPrivateValue(); + // $ExpectType number + instance.c; + + const call = ctor.mock.calls[0]; + // $ExpectType [a: number, b: number] + call.arguments; + // $ExpectType Clazz | undefined + call.result; + // $ExpectType typeof Clazz + call.target; +}); + +test('spies on a getter', (t) => { + const obj = { + prop: 5, + get method() { + return this.prop; + }, + }; + + { + const getter = t.mock.method(obj, 'method', { getter: true }); + console.log(obj.method); + const call = getter.mock.calls[0]; + + // $ExpectType unknown[] + call.arguments; + // $ExpectType unknown + call.result; + // $ExpectType undefined + call.target; + // $ExpectType unknown + call.this; + } + { + const getter = t.mock.getter(obj, 'method'); + console.log(obj.method); + const call = getter.mock.calls[0]; + + // $ExpectType [] + call.arguments; + // $ExpectType number | undefined + call.result; + // $ExpectType undefined + call.target; + // $ExpectType unknown + call.this; + } +}); + +test('mocks a getter', (t) => { + const obj = { + prop: 5, + get method() { + return this.prop; + }, + }; + + function mockMethod(this: typeof obj) { + return this.prop - 1; + } + + { + const getter = t.mock.method(obj, 'method', mockMethod, { getter: true }); + console.log(obj.method); + const call = getter.mock.calls[0]; + + // $ExpectType unknown[] + call.arguments; + // $ExpectType unknown + call.result; + // $ExpectType undefined + call.target; + // $ExpectType unknown + call.this; + } + { + const getter = t.mock.getter(obj, 'method', mockMethod); + console.log(obj.method); + const call = getter.mock.calls[0]; + + // $ExpectType [] + call.arguments; + // $ExpectType number | undefined + call.result; + // $ExpectType undefined + call.target; + // $ExpectType unknown + call.this; + } +}); + +test('spies on a setter', (t) => { + const obj = { + prop: 100, + set method(val: number) { + this.prop = val; + }, + }; + + { + const setter = t.mock.method(obj, 'method', { setter: true }); + obj.method = 77; + const call = setter.mock.calls[0]; + + // $ExpectType unknown[] + call.arguments; + // $ExpectType unknown + call.result; + // $ExpectType undefined + call.target; + // $ExpectType unknown + call.this; + } + { + const setter = t.mock.setter(obj, 'method'); + obj.method = 77; + const call = setter.mock.calls[0]; + + // $ExpectType [value: number] + call.arguments; + // $ExpectType void | undefined + call.result; + // $ExpectType undefined + call.target; + // $ExpectType unknown + call.this; + } +}); + +test('mocks a setter', (t) => { + const obj = { + prop: 100, + set method(val: number) { + this.prop = val; + }, + }; + + function mockMethod(this: typeof obj, value: number) { + this.prop = -value; + } + + { + const setter = t.mock.method(obj, 'method', mockMethod, { setter: true }); + obj.method = 77; + const call = setter.mock.calls[0]; + + // $ExpectType unknown[] + call.arguments; + // $ExpectType unknown + call.result; + // $ExpectType undefined + call.target; + // $ExpectType unknown + call.this; + } + { + const setter = t.mock.setter(obj, 'method', mockMethod); + obj.method = 77; + const call = setter.mock.calls[0]; + + // $ExpectType [value: number] | [value: number] + call.arguments; + // $ExpectType void | undefined + call.result; + // $ExpectType undefined + call.target; + // $ExpectType unknown + call.this; + } +});