Repository navigation
test_runner: mock.mockImplementationOnce only works for the last call #47718
Description
Activity
- changed the title
[-]test_runner: mock.mockImplementationOnce only works for the first call[/-][+]test_runner: mock.mockImplementationOnce only works for the last call[/+]on Apr 25, 2023 I think this is working as intended/documented.
mockImplementationOnce()by default only changes the behavior of the next invocation. There is an option second parameter,onCall, that allows you to change that default behavior.I think this is working as intended/documented.
mockImplementationOnce()by default only changes the behavior of the next invocation. There is an option second parameter,onCall, that allows you to change that default behavior.Hmm got it! Why don't put an internal counter to avoid having this:
m.mock.mockImplementationOnce(() => 1, 0) m.mock.mockImplementationOnce(() => 2, 1)
to this
m.mock.mockImplementationOnce(() => 1) m.mock.mockImplementationOnce(() => 2)
and even enable nested calls such as:
m.mock .mockImplementationOnce(() => 1) .mockImplementationOnce(() => 2)
Other mocking libraries such as Jest work like this so I think for dev experience would be best to follow the same idea.
WDYT?
I'm not opposed if someone can make it work well, but there are edge cases that will need to be addressed. I don't think it's as straightforward as incrementing a counter when
mockImplementationOnce()is called:const m = mock.method(...); m(); // Original mock provided by mock.method() m.mock.mockImplementationOnce(() => 1); m.mock.mockImplementationOnce(() => 2); m(); // Returns 1 m(); // Returns 2 m(); // Original mock provided by mock.method() m(); // Original mock provided by mock.method() // What should the next line do? m.mock.mockImplementationOnce(() => 3); m();
In this example, I would expect the final
mockImplementationOnce()to mock the next invocation. If the counter were only incremented by calls tomockImplementationOnce()then the invocation would have already passed (which throws an exception). The proposed counter would need to be carefully managed.Jest work like this so I think for dev experience would be best to follow the same idea
Side note: To be clear, just because Jest does something a particular way does not mean we should copy it.
- addedtest_runnerIssues and PRs related to the test runner subsystem.Issues and PRs related to the test runner subsystem.
on Apr 25, 2023 I'm not opposed if someone can make it work well, but there are edge cases that will need to be addressed. I don't think it's as straightforward as incrementing a counter when
mockImplementationOnce()is called:const m = mock.method(...); m(); // Original mock provided by mock.method() m.mock.mockImplementationOnce(() => 1); m.mock.mockImplementationOnce(() => 2); m(); // Returns 1 m(); // Returns 2 m(); // Original mock provided by mock.method() m(); // Original mock provided by mock.method() // What should the next line do? m.mock.mockImplementationOnce(() => 3); m();
In this example, I would expect the final
mockImplementationOnce()to mock the next invocation. If the counter were only incremented by calls tomockImplementationOnce()then the invocation would have already passed (which throws an exception). The proposed counter would need to be carefully managed.Jest work like this so I think for dev experience would be best to follow the same idea
Side note: To be clear, just because Jest does something a particular way does not mean we should copy it.
Nice I got it!
does not mean we should copy it
Yes, I agree with you. I only mention them as I like how the API works and thought it'd be easier to follow the same pattern.
I'm gonna try doing a PoC and open a draft PR for it so we could discuss along the way
- addedfeature requestIssues requesting new Node.js features.Issues requesting new Node.js features.
on May 10, 2023 Side note: possible implementation is having a queue for the mocks, this way this example would work as expected
const m = mock.method(...); m(); // Original mock provided by mock.method() m.mock.mockImplementationOnce(() => 1); m.mock.mockImplementationOnce(() => 2); m(); // Returns 1 m(); // Returns 2 m(); // Original mock provided by mock.method() m(); // Original mock provided by mock.method() // What should the next line do? m.mock.mockImplementationOnce(() => 3); m();
Reacted by Erick Wendel and Fernando PasikI think it's a valuable feature
Can this be reopened?
I thought of a workaround using external state to the mocked implementaiton, but seems hacky, please if anybody has a better suggestion I'd appreciate
const queueMock = [1, 2] m.mock.mockImplementation(() => queueMock.shift())Other testing frameworks support the queue/chain way to implement once
https://vitest.dev/api/mock.html#mockimplementationonce
https://jestjs.io/docs/mock-function-api#mockfnmockimplementationoncefn
Version
v21.0.0-pre
Platform
Darwin MacBook-Pro-4.local 21.6.0 Darwin Kernel Version 21.6.0: Mon Aug 22 20:19:52 PDT 2022; root:xnu-8020.140.49~2/RELEASE_ARM64_T6000 arm64
Subsystem
No response
What steps will reproduce the bug?
How often does it reproduce? Is there a required condition?
Always
What is the expected behavior? Why is that the expected behavior?
mock.mockImplementationOnce should be used to mock each individual call.
given
Each call result should be returned given the order configured by the mockImplementation Once
What do you see instead?
Only the last mock.mockImplementationOnce is applied.
Additional information
I'd also enable sequence calls like:
@nodejs/test_runner