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 type inference #32579

Merged
merged 14 commits into from Feb 5, 2019

Conversation

antoinebrault
Copy link
Contributor

Follow-up of #31094

Updated jest typings to use latest typescript features to infer mock types.

Breaking Change

With types inferred, mockImplementation(), mockReturnValue(), mock...() will not compile if they are not used with the right types.


Please fill in this template.

  • Use a meaningful title for the pull request. Include the name of the package modified.
  • Test the change in your own code. (Compile and run.)
  • Add or edit tests to reflect the change. (Run with npm test.)
  • Follow the advice from the readme.
  • Avoid common mistakes.
  • Run npm run lint package-name (or tsc if no tslint.json is present).

Select one of these and delete the others:

If changing an existing definition:

  • Provide a URL to documentation or source code which provides context for the suggested changes: private project
  • Increase the version number in the header if appropriate.
  • If you are making substantial changes, consider adding a tslint.json containing { "extends": "dtslint/dt.json" }.

@antoinebrault antoinebrault mentioned this pull request Jan 29, 2019
9 tasks
@typescript-bot typescript-bot added this to Waiting for Reviewers in Pull Request Status Board Jan 29, 2019
@typescript-bot typescript-bot added Popular package This PR affects a popular package (as counted by NPM download counts). Awaiting reviewer feedback labels Jan 29, 2019
@typescript-bot
Copy link
Contributor

typescript-bot commented Jan 29, 2019

@antoinebrault Thank you for submitting this PR!

🔔 @JoshuaKGoldberg @tkrotoff @cwoodland @johnny4753 @NoHomey @jwbay @asvetliakov @alexjoverm @epicallan @ikatyang @wsmd @JamieMason @douglasduteil @ahnpnl @JoshuaKGoldberg @UselessPickles @r3nya @Hotell @sebald @andys8 @dawnmist @geovanisouza92 @deadNightTiger @joscha @jonasheinrich @aldentaylor @bradleyayers - please review this PR in the next few days. Be sure to explicitly select Approve or Request Changes in the GitHub UI so I know what's going on.

If no reviewer appears after a week, a DefinitelyTyped maintainer will review the PR instead.

@typescript-bot typescript-bot moved this from Waiting for Reviewers to Review in Pull Request Status Board Feb 3, 2019
@typescript-bot typescript-bot added the Unmerged The author did not merge the PR when it was ready. label Feb 3, 2019
@typescript-bot
Copy link
Contributor

typescript-bot commented Feb 3, 2019

A definition owner has approved this PR ⭐️. A maintainer will merge this PR shortly. If it shouldn't be merged yet, please leave a comment saying so and we'll wait. Thank you for your contribution to DefinitelyTyped!

@@ -11,8 +11,8 @@ function subtract(minuend: number, subtrahend: number) {
}

beforeEach(() => {
jest.spyOn(global, 'describe').mockImplementation((title, fn) => fn());
jest.spyOn(global, 'test').mockImplementation((name, fn) => fn());
jest.spyOn(global, 'describe').mockImplementation((title, fn) => jest.fn());
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you comment on the change here? Is this because fn is now correctly typed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Don't know why I did that... I updated the PR.

fn should be cast to a function because it is unknown

 jest.spyOn(global, 'describe').mockImplementation((title, fn) => (fn as () => void)());

@@ -35,6 +36,8 @@ declare var xtest: jest.It;

declare const expect: jest.Expect;

type ArgsType<T> = T extends (...args: infer A) => any ? A : never;
Copy link
Contributor

Choose a reason for hiding this comment

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

TS ships with a Parameters type for this. Was it added after 3.0?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was added in 3.1 (microsoft/TypeScript@60b4fe9)

Thanks for the info. I didn't even know about it since it's not in https://www.typescriptlang.org/docs/handbook/advanced-types.html

ERROR: 218:145  expect  Compile error in typescript@3.0 but not in typescript@3.1.
Fix with a comment '// TypeScript Version: 3.1' just under the header.
Cannot find name 'Parameters'.

const spy6 = jest.spyOn(spiedTarget2, "value", "set");

let spy7: jest.SpyInstance;
spy7 = jest.spyOn(spiedTarget, "setValue");
Copy link
Contributor

Choose a reason for hiding this comment

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

What is this one trying to test since nothing is done with spy7?

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 testing if spyOn can be assigned to default generics of jest.SpyInstance. spy4 is basically the same. I'll remove spy7 from the tests.

Copy link
Contributor

@jwbay jwbay left a comment

Choose a reason for hiding this comment

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

Just a couple questions, but everything looks shippable to me. Love all the $ExpectTypes in the tests.

@typescript-bot typescript-bot moved this from Review to Check and Merge in Pull Request Status Board Feb 3, 2019
@typescript-bot typescript-bot added Owner Approved A listed owner of this package signed off on the pull request. Merge:Express and removed Awaiting reviewer feedback Unmerged The author did not merge the PR when it was ready. labels Feb 3, 2019
@armanio123 armanio123 merged commit 9bb7883 into DefinitelyTyped:master Feb 5, 2019
@typescript-bot
Copy link
Contributor

I just published @types/expect-puppeteer@3.3 to npm.

@typescript-bot
Copy link
Contributor

I just published @types/frisby@2.0 to npm.

@typescript-bot
Copy link
Contributor

I just published @types/jest-axe@2.2 to npm.

@typescript-bot
Copy link
Contributor

I just published @types/jest-in-case@1.0 to npm.

@typescript-bot
Copy link
Contributor

I just published @types/jest-json-schema@1.2 to npm.

@typescript-bot
Copy link
Contributor

I just published @types/jest-matchers@20.0 to npm.

@typescript-bot
Copy link
Contributor

I just published @types/jest-plugin-context@2.9 to npm.

@typescript-bot
Copy link
Contributor

I just published @types/jest-specific-snapshot@0.5 to npm.

@typescript-bot
Copy link
Contributor

I just published @types/jest-when@1.1 to npm.

@typescript-bot
Copy link
Contributor

I just published @types/storybook__addon-storyshots@3.4 to npm.

@typescript-bot typescript-bot removed this from Check and Merge in Pull Request Status Board Feb 5, 2019
@Jessidhia
Copy link
Member

The bot is becoming more clever 😮

Would be nice to just post a single time and edit the post as it progress, or only post once with the entire list:

I just published to NPM

<details><summary>9/9 packages</summary>

``\`
...
@types/jest-when@1.1
@types/storybook__addon-storyshots@3.4
``\`
</details>

I can see this being a problem should react ever bump minimum typescript version, for example...

@armanio123
Copy link
Contributor

Yeah, something we could definitely improve. I'll talk to the team about it and see what can be done.

Thanks for contributing.

@Chris-V
Copy link

Chris-V commented Feb 5, 2019

This update is absolutely fantastic. I am having a couple issues with the implementation though.

  1. When using Mocked<T, Y>, T[P] is mapped to the exact member type rather to (what I believe should be) the return type when it is a function.
  2. The promise helpers (mockResolvedValue, ...) should unwrap the promise's argument type.

Here's a playground link to test it out: https://bit.ly/2WJN5F9 I also added some code below with a proper test syntax:

Test case
api.ts

interface Something {
    a: number;
}
class Api {
    async performAction(x: Something): Promise<Something> { /* do whatever */ }
}

api.spec.ts:

jest.mock('./api.ts');

import { Api } from './api';

describe('Demo', () => {
    let api: jest.Mocked<Api>;

    beforeEach(() => {
        api = new Api() as any;
    })

    it('should compile', () => {
        api.performAction.mockImplementation(() => Promise.resolve({ a: 1 }));
        api.performAction.mockReturnValue(Promise.resolve({ a: 2 }));
        api.performAction.mockResolvedValue({ a: 3 });
    });
});

Suggestion
So my suggestion is to extract the return type if T[P] is a function. I also suggest tweaking the promise helpers, otherwise they expect Promises as parameters which defies the intent.

type Mocked<T> = {
    [P in keyof T]: T[P] & MockInstance<T[P] extends (...args) => any ? ReturnType<T[P]> : T[P], ArgsType<T[P]>>;
} & T;

interface MockInstance<T, Y extends any[]> {
    mockResolvedValue(value: T extends PromiseLike<infer U> ? U : never): Mock<T, Y>;
    // same for resolvedOnce, reject, rejectOnce
}

@antoinebrault
Copy link
Contributor Author

antoinebrault commented Feb 5, 2019

You're right. I'll look at it and add tests tonight.

  1. I think it could be simplified:
type Mocked<T> = {
    [P in keyof T]: T[P] extends (...args) => any ? MockInstance<ReturnType<T[P]>, ArgsType<T[P]>>: T[P];
} & T;
  1. I agree mockResolvedValue should unwrap. A better impl could be:
// considering this: await Promise.resolve(Promise.resolve(3)) === 3

mockResolvedValue(value: T extends PromiseLike<infer U> ? U | T : T | Promise<T>): Mock<T extends PromiseLike<any> ? T : Promise<T>, Y>;

https://bit.ly/2TyFXsW

@SimenB
Copy link
Contributor

SimenB commented Feb 7, 2019

This is super awesome work - I had no idea TS would be able to infer as much as it's able to! 🎉

I'll be a bit sneaky and try to recruit help here.

We're currently migrating Jest itself to TypeScript (jestjs/jest#7807), and I'd love some help converting jest-mock. That's the underlying implementation behind jest.fn(), jest. genMockFromModule(), jest.spyOn et al..

@superhawk610
Copy link
Contributor

Looks like this broke ts-jest's mocked helper (see here). I'm working on upgrading the typings there to support these new inferences, any help would be 🔥

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Owner Approved A listed owner of this package signed off on the pull request. Popular package This PR affects a popular package (as counted by NPM download counts).
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants