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

feat(@jest/mock): Add withImplementation #13281

Merged
merged 16 commits into from Sep 23, 2022

Conversation

jeppester
Copy link
Contributor

Summary

A follow-up for #9270

It's often useful to override mock implementations for specific tests, and mockImplementationOnce is there to help out.

It is however not a very elegant solution for situations where the mock will get called multiple times:

  • It's not intuitive that you can call the method multiple times to "plan" for multiple calls
  • The number of calls to the mock might be unimportant to the test. mockImplementationOnce makes the number of calls unecessarily important.
  • The number of calls to the mock becomes part of the "arrange" phase of the test rather than the "assert" phase.
  • mockImplementationOnce will bleed into the next test if the implementation does not end up getting called.

This PR implements a new method withImplementation which sets a temporary default implementation which will be available within a callback, after which the default implementation will be set back to what it was before. If the callback returns a promise, withImplemenation will return a promise that can be awaited in the test.

I had to add a couple of @ts-expect-error comments to get the returned value of withImplementation (promise or void) match the return value of the callback implementation. If you know a better way of achieving this, let me know.

Also, I could potentially be useful to print a warning if the callback does not trigger a call to the mock - in which case withImplementation was redundant. What do you think about that idea?

Test plan

I've added unit tests for the feature. Let me know if I need to add more tests.

For temporarily overriding mock implementations.
@mrazauskas
Copy link
Contributor

Could you add type tests, please? For completeness and to make sure that generic types will work as expected for the user.

The tests live in this file. To run them build the library and run yarn test-types. Remember to rebuild after each change. If you are using IDE, ignore the red errors the test file. These are caught by expectError.

docs/MockFunctionAPI.md Outdated Show resolved Hide resolved
@jeppester
Copy link
Contributor Author

I believe all the feedback has been addressed now.
Let me know if I need to do more.

Co-authored-by: Tom Mrazauskas <tom@mrazauskas.de>
@jeppester
Copy link
Contributor Author

@mrazauskas
I believe there are no issues left. What is the next step from here?

@mrazauskas
Copy link
Contributor

Right, all looks good to me. So we have to wait for @SimenB, because only he is able to merge PRs.

@SimenB
Copy link
Member

SimenB commented Sep 23, 2022

Also, I could potentially be useful to print a warning if the callback does not trigger a call to the mock - in which case withImplementation was redundant. What do you think about that idea?

I like that idea!

Copy link
Member

@SimenB SimenB left a comment

Choose a reason for hiding this comment

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

nice stuff!

CHANGELOG.md Outdated Show resolved Hide resolved
docs/MockFunctionAPI.md Outdated Show resolved Hide resolved
packages/jest-mock/README.md Outdated Show resolved Hide resolved
packages/jest-mock/src/__tests__/index.test.ts Outdated Show resolved Hide resolved
packages/jest-mock/src/__tests__/index.test.ts Outdated Show resolved Hide resolved
Comment on lines 793 to 794
typeof returnedValue === 'object' &&
returnedValue !== null &&
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
typeof returnedValue === 'object' &&
returnedValue !== null &&
returnedValue != null &&
typeof returnedValue === 'object' &&

probably doesn't matter, but easier to bail out early

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. Like I mentioned in one of my previous comments I copied this verbatim from jest circus.

So there's probably a nice tiny performance enhancement waiting to be made there as well:
https://github.com/facebook/jest/blob/a20fd859673800c50f8f089cfb4a87faec119525/packages/jest-circus/src/utils.ts#L271-L275

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

haha, the linked SO answer says not to use it 🙈

Copy link
Member

Choose a reason for hiding this comment

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

I opened #13314

jeppester and others added 5 commits September 23, 2022 14:24
Co-authored-by: Simen Bekkhus <sbekkhus91@gmail.com>
Co-authored-by: Simen Bekkhus <sbekkhus91@gmail.com>
Remove redundant `async`

Co-authored-by: Simen Bekkhus <sbekkhus91@gmail.com>
Change promise detection expression to bail out earlier for `null` and `undefined`

Co-authored-by: Simen Bekkhus <sbekkhus91@gmail.com>
testFn();
expect(mock1).toHaveBeenCalled();

expect.assertions(3);
Copy link
Contributor

Choose a reason for hiding this comment

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

Just wondering: should expect.assertions be added to the examples in docs as well? I mean, if users must use expect inside the callback, it would be good to show that in the example. Or?

Copy link
Contributor Author

@jeppester jeppester Sep 23, 2022

Choose a reason for hiding this comment

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

Since the callback function will always be executed immediately by withImplementation - no matter if it's async or not - I wouldn't think expect.assertions is necessary outside of jest's own test suite. 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, right. Got it (;

@jeppester
Copy link
Contributor Author

I might be wrong but to me it seems that the failing job random and not related to the PR.
If you agree with that assumption, can I get you to rerun the job?

If that job succeeds I believe this PR is ready to merge. - that is unless you have more comments.

Btw. thank you for the thorough, kind, and useful reviews 👍, it's a great experience to contribute.

@SimenB
Copy link
Member

SimenB commented Sep 23, 2022

Yeah, you can safely ignore that failure 👍

I'll land #13314, then merge main into this PR, then I'll land this one.

Thanks for the patience and great contribution! 🎉

@SimenB SimenB merged commit 35e8b6a into jestjs:main Sep 23, 2022
@jeppester jeppester deleted the feature/with-implementation branch September 26, 2022 06:37
@SimenB
Copy link
Member

SimenB commented Sep 28, 2022

@github-actions
Copy link

This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 30, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants