Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[jest] add jest.Spied* utility types #62781

Merged
merged 2 commits into from Nov 4, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
45 changes: 37 additions & 8 deletions types/jest/index.d.ts
Expand Up @@ -389,18 +389,18 @@ declare namespace jest {
: Value extends Func
? SpyInstance<ReturnType<Value>, ArgsType<Value>>
: never;
function spyOn<T extends {}, M extends FunctionPropertyNames<Required<T>>>(
object: T,
method: M,
): FunctionProperties<Required<T>>[M] extends Func
? SpyInstance<ReturnType<FunctionProperties<Required<T>>[M]>, ArgsType<FunctionProperties<Required<T>>[M]>>
: never;
function spyOn<T extends {}, M extends ConstructorPropertyNames<Required<T>>>(
object: T,
method: M,
): Required<T>[M] extends new (...args: any[]) => any
? SpyInstance<InstanceType<Required<T>[M]>, ConstructorArgsType<Required<T>[M]>>
: never;
function spyOn<T extends {}, M extends FunctionPropertyNames<Required<T>>>(
Copy link
Contributor

@dlech dlech Nov 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Swapping the order here breaks this use case:

jest.spyOn(Storage.prototype, 'getItem').mockImplementation((key) => null);
ERROR: 570:42  expect  TypeScript@5.0 compile error: 
Property 'mockImplementation' does not exist on type 'never'.

undoing this change breaks

let typedSpy1: jest.SpiedClass<typeof globalThis.Date>;
typedSpy1 = jest.spyOn(globalThis, 'Date');

How can we make both work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #63182. Seems like it will make both cases work as expected.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened #63196 to address this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed that one, thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to add. As you noticed swapping the order made spyOn work correctly with Date. The type of Date is rather odd indeed, but the same could be said about Storage. Not so easy to cover both.

object: T,
method: M,
): FunctionProperties<Required<T>>[M] extends Func
? SpyInstance<ReturnType<FunctionProperties<Required<T>>[M]>, ArgsType<FunctionProperties<Required<T>>[M]>>
: never;
/**
* Indicates that the module system should never return a mocked version of
* the specified module from require() (e.g. that it should always return the real module).
Expand Down Expand Up @@ -1166,9 +1166,38 @@ declare namespace jest {
interface SpyInstance<T = any, Y extends any[] = any> extends MockInstance<T, Y> {}

/**
* Represents a function that has been spied on.
* Constructs the type of a spied class.
*/
type SpiedFunction<T extends (...args: any[]) => any> = SpyInstance<ReturnType<T>, ArgsType<T>>;
type SpiedClass<T extends abstract new (...args: any) => any> = SpyInstance<
InstanceType<T>,
ConstructorParameters<T>
>;

/**
* Constructs the type of a spied function.
*/
type SpiedFunction<T extends (...args: any) => any> = SpyInstance<ReturnType<T>, ArgsType<T>>;

/**
* Constructs the type of a spied getter.
*/
type SpiedGetter<T> = SpyInstance<T, []>;

/**
* Constructs the type of a spied setter.
*/
type SpiedSetter<T> = SpyInstance<void, [T]>;

/**
* Constructs the type of a spied class or function.
*/
type Spied<T extends (abstract new (...args: any) => any) | ((...args: any) => any)> = T extends abstract new (
...args: any
) => any
? SpiedClass<T>
: T extends (...args: any) => any
? SpiedFunction<T>
: never;

/**
* Wrap a function with mock definitions
Expand Down
15 changes: 15 additions & 0 deletions types/jest/jest-tests.ts
Expand Up @@ -674,6 +674,21 @@ jest.spyOn(spyWithIndexSignatureImpl, 'prop');
// $ExpectType SpyInstance<{ some: string; }, []>
jest.spyOn(spyWithIndexSignatureImpl, 'prop', 'get');

let typedSpy: jest.Spied<typeof spiedTarget.returnsVoid>;
typedSpy = jest.spyOn(spiedTarget, 'returnsVoid');

let typedSpy1: jest.SpiedClass<typeof globalThis.Date>;
typedSpy1 = jest.spyOn(globalThis, 'Date');

let typedSpy2: jest.SpiedFunction<typeof spiedTarget.setValue>;
typedSpy2 = jest.spyOn(spiedTarget, 'setValue');

let typedSpy3: jest.SpiedGetter<typeof spiedTarget2.value>;
typedSpy3 = jest.spyOn(spiedTarget2, 'value', 'get');

let typedSpy4: jest.SpiedSetter<typeof spiedTarget2.value>;
typedSpy4 = jest.spyOn(spiedTarget2, 'value', 'set');

// $ExpectType MockedObjectDeep<{}>
jest.mocked({});
// $ExpectType MockedObjectDeep<{}>
Expand Down