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

[@types/jest] spyOn Argument is not assignable to parameter of type #33173

Closed
3 tasks done
k2snowman69 opened this issue Feb 18, 2019 · 12 comments · Fixed by #33214
Closed
3 tasks done

[@types/jest] spyOn Argument is not assignable to parameter of type #33173

k2snowman69 opened this issue Feb 18, 2019 · 12 comments · Fixed by #33214

Comments

@k2snowman69
Copy link

  • I tried using the @types/jest package and had problems. (Version 24.0.6)
  • I tried using the latest stable version of tsc. https://www.npmjs.com/package/typescript (Version 3.3.3)
  • I have a question that is inappropriate for StackOverflow. (Please ask any appropriate questions there).

Since @types/jest@24.0.* has been released I've been running into a typing issue where spyOn can't seem to pick up the correct property out of the object that's being spied on. Here is a short piece of sample code:

export interface PopoverProps {
  wrapperElementProps?: any;
  onOpenChange?: (isOpen: boolean) => void;
}

let spy: jest.SpyInstance<void, [boolean]>;
let defaultProps: PopoverProps = {
  onOpenChange: () => {}
};
spy = jest.spyOn(defaultProps, "onOpenChange");

Which results in the following error:

    src/popover2.test.tsx:10:32 - error TS2345: Argument of type '"onOpenChange"' is not assignable to parameter of type '"wrapperElementProps"'.

    10 spy = jest.spyOn(defaultProps, "onOpenChange");
                                      ~~~~~~~~~~~~~~

I think the issue is around type ArgsType<T> = T extends (...args: infer A) => any ? A : never; but that just maybe me projecting my lack of knowledge around how infer actually works.

@asvetliakov
Copy link
Contributor

why can't you just use?

let defaultProps: PopoverProps = {
  onOpenChange: jest.fn(),
};

@k2snowman69
Copy link
Author

k2snowman69 commented Feb 18, 2019

In 23, spyOn would return a typed version of the method being mocked where fn() returns merely a mock of <any, any> unless you explicitly type it which is why I've preferred spyOn so far (as it determined the types for you)

@asvetliakov
Copy link
Contributor

asvetliakov commented Feb 18, 2019

Well, what about this? (if i understood your type needs correctly):

let defaultProps: jest.Mocked<PopoverProps> = {
  onOpenChange: jest.fn(),
};
defaultProps.onOpenChange.mock // typed mock, i.e. jest.MockInstance<void, [boolean]>

@k2snowman69
Copy link
Author

I don't disagree that there are ways to work around the issue I think the purpose of me opening this bug is to determine why the specific typing of spyOn seems to have a typing failure. If instead the outcome you're alluding to is to drop support for spyOn in favor for jest.fn() then maybe the option here is to remove spyOn from the types?

@antoinebrault
Copy link
Contributor

I'll look at it. Seems weird to me that it's working with objects but not interfaces. Maybe it's a TypeScript bug.

@k2snowman69
Copy link
Author

k2snowman69 commented Feb 18, 2019

Digging into this it seems the issue is that the type is optional which is causing the "extends" functionality of which determines which spyOn to use to not work. What is weird about this specific case is if you go through a generic (e.g. ArgsType) it works successfully but attempting to pick out the types directly doesn't. For example:

      type prop1 = (isOpen: boolean) => void;
      type argsInfered1 = prop1 extends (...args: infer A) => any ? A : never;
      // argsInfered1 = [boolean] - Correct

      type prop2 = undefined | ((isOpen: boolean) => void);
      type argsInfered2 = prop2 extends (...args: infer A) => any ? A : never;
      // argsInfered2 = never - Incorrect

      type ArgsType<T> = T extends (...args: infer A) => any ? A : never;
      let argsInferredDirectly1: ArgsType<prop1>;
      // argsInferredDirectly1 = [boolean] - Matches argsInfered1

      let argsInferredDirectly2: ArgsType<prop2>;
      // argsInferredDirectly2 = [boolean] - Shouldn't this match argsInfered2?

Hopefully this helps when investigating... Fairly sure I'm at the length of my typescript knowledge now :-)

@antoinebrault
Copy link
Contributor

antoinebrault commented Feb 18, 2019

The optional field makes the conditional on array bug.

function test<T extends {}, M extends keyof T>(object: T, method: M): T[M] extends (...args: any) => any ? T[M] : never;

export interface PopoverProps {
  wrapperElementProps?: any;
  onOpenChange?: (isOpen: boolean) => void;
}
let defaultProps: PopoverProps = {
  onOpenChange: () => {}
};
test(defaultProps, 'onOpenChange') // returns never instead of (isOpen: boolean) => void

@antoinebrault
Copy link
Contributor

This seems to work. I'll add more tests.

function spyOn<T extends {}, Y extends Required<T>, M extends FunctionPropertyNames<Y>>(object: T, method: M): Y[M] extends (...args: any[]) => any ? SpyInstance<ReturnType<Y[M]>, ArgsType<Y[M]>> : never;

@nithincvpoyyil
Copy link

nithincvpoyyil commented May 3, 2019

@antoinebrault : 👍 Thank you for fixing this issue. If possible, could you please update the example in the definition file with proper typing.

@Elias-Graf
Copy link
Contributor

Elias-Graf commented Sep 22, 2021

I'm getting something similar to this when doing:

import * as reactNavigation from '@react-navigation/core';

// ...

const useRouter = jest.spyOn(reactNavigation, 'useRouter');

It results in:

No overload matches this call.
  Overload 1 of 4, '(object: typeof import("/.../node_modules/@react-navigation/core/lib/typescript/src/index"), method: FunctionPropertyNames<Required<typeof import("/.../node_modules/@react-navigation/core/lib/typescript/src/index")>>): SpyInstance<...>', gave the following error.
    Argument of type '"useRouter"' is not assignable to parameter of type 'FunctionPropertyNames<Required<typeof import("/.../node_modules/@react-navigation/core/lib/typescript/src/index")>>'.
  Overload 2 of 4, '(object: typeof import("/.../node_modules/@react-navigation/core/lib/typescript/src/index"), method: "PrivateValueStore"): SpyInstance<PrivateValueStore<unknown, unknown, unknown>, []>', gave the following error.
    Argument of type '"useRouter"' is not assignable to parameter of type '"PrivateValueStore"'.ts(2769)

I'm using @react-navigation/native@5.9.3, jest@26.2.2, ts-jest@26.5.5, @types/jest@26.0.8.

According to #33214 (comment) @antoinebrault merged a fix in version 24.0.9.

I would be really glad if anyone could tell my why this is happening (again?) because the only solution I see right now is using // @ts-ignore, which is really not something I would want to do.

Edit:

Even if I do:

// FIXME: remove ts-ignore
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const useRouter = jest.spyOn(reactNavigation, 'useRouter');

this wont work:

useRouter.mockReturnValue({key: '', name: '', params: {/* ... */}});

because it thinks that it's assigning the mocked return value to PrivateValueStore (or whatever)...

Edit 2:

While not as nice as spyOn, this works as a fix/workaround:

import * as reactNavigation from '@react-navigation/core';

jest.mock('@react-navigation/core', () => ({
	...(jest.requireActual('@react-navigation/core') as Record<string, unknown>),
	useRoute: jest.fn(),
}));

(reactNavigation.useRoute as jest.Mock).mockReturnValue({
	...jest.requireActual('@react-navigation/core').useRoute,
	params: {/* ... */},
});

@RamonSchmitt
Copy link

This works for me

jest.spyOn<MyComponent, any>(component,'myMethod');

But I haven't been able to fix the any.

@ahmed-dardery
Copy link

@RamonSchmitt
I've used

interface HasMyMethod {
  myMethod(param: paramType): returnType;
}
spyOn(component as HasMyMethod, 'myMethod');

To avoid using any.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants