feat(vitest): create vi.when()#10174
Conversation
✅ Deploy Preview for vitest-dev ready!Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify project configuration. |
607d2d3 to
96e8997
Compare
| /** | ||
| * Defines behavior for a specific set of arguments. | ||
| * | ||
| * Multiple behaviors can be stacked for the same arguments. They are matched last-registered first (LIFO stack, last in first out), so earlier entries act as fallbacks once later ones are exhausted. |
There was a problem hiding this comment.
Should this be LIFO or FIFO? 👀
There was a problem hiding this comment.
The doc example doesn't read well to me, so I want FIFO.
* @example
* // Stack behaviors: first call returns `false`, subsequent calls return `true`
* vi.when(spy)
* .calledWith('darkMode')
* .thenReturn(true)
* .thenReturnOnce(false)There was a problem hiding this comment.
So here you would consider thenReturnOnce to be unreachable?
You'd write it like this?
vi.when(spy)
.calledWith('darkMode')
.thenReturnOnce(false)
.thenReturn(true)I wonder what others think of this, as the fluent/builder APIs I've used were all LIFO-style, but the other mock APIs do seem to be FIFO.
There was a problem hiding this comment.
Not sure, but from my limited english interpretation, the API suggest it should naturally read like:
when spy is called with x, then return y once, then return z (forever).
vi.when(spy)
.calledWith(x)
.thenReturnOnce(y)
.thenReturn(z)so current order seems inverted.
There was a problem hiding this comment.
This is to be decided either by internal or public pole.
macarie
left a comment
There was a problem hiding this comment.
I have a small recursive package issue... I think when should live in packages/spy, but it needs equals from pacakges/expect, which depends on packages/spy...
Where would it make the most sense to have when? Because right now expect can't use the helper function as it lives in pacakges/vitest 🙃
| export { JestChaiExpect } from './jest-expect' | ||
| export { JestExtend } from './jest-extend' | ||
| export { addCustomEqualityTesters } from './jest-matcher-utils' | ||
| export { addCustomEqualityTesters, getCustomEqualityTesters /* @todo */ } from './jest-matcher-utils' |
There was a problem hiding this comment.
Is there a function that already retrieves the custom matchers and uses it?
There was a problem hiding this comment.
I think we can just move it to utils/equals
| IntersectionObserver === undefined | ||
| ``` | ||
|
|
||
| ### vi.when <Version type="experimental">5.0</Version> <Experimental /> {#vi-when} |
There was a problem hiding this comment.
I think it doesn't need to be experimental in v5
| * // calledWith(2) | ||
| * // ✗ thenReturn(42) never called | ||
| */ | ||
| '~getDiagnostics': () => { |
There was a problem hiding this comment.
I think we can use _. The reason why ~ is usually chosen is because it's the last in the list of public APIs, but we have @internal flag which removes this method from the bundle, its types are only available in this repo
| } | ||
| } | ||
|
|
||
| function isExhausted(action: Behavior<unknown[], unknown>['actions'][number]): boolean { |
There was a problem hiding this comment.
Can we just have a separate type instead of doing archeology in Behavior<unknown[], unknown>['actions'][number]?
|
|
||
| expect(d.pendingBehaviors).toMatchInlineSnapshot(` | ||
| "calledWith("a", 0) | ||
| ✗ thenReturn(97, { times: 2 }) 2 remaining (of 2) |
There was a problem hiding this comment.
Shouldn't it be 2 remaining (out of 2)?
|
|
||
| Defines per-argument behaviors on a spy, replacing its implementation for the duration of the `when` chain. | ||
|
|
||
| Call `.calledWith(...args)` on the returned object to specify which call arguments to match, then chain one or more `then*` methods to declare what the spy should return, throw, or resolve when invoked with those arguments. Arguments are matched with deep equality and support asymmetric matchers such as `expect.any()`. |
There was a problem hiding this comment.
What's the behavior of hanging calledWith without thenXxx? For example, I'm wondering if it's expected use case to only use calledWith to combine with toHaveBeenExhausted e.g.
const spy = vi.fn()
const w = vi.when(spy).calledWith(1)
expect(w).not.toHaveBeenExhausted()
spy(1)
expect(w).toHaveBeenExhausted()There was a problem hiding this comment.
It will use the provided fallback action/function. By default it would call the original spy implementation (which in this case is a noop).
There's indeed a bug with this, as the assertion would work even if it shouldn't. I wonder if it still makes sense for toHaveBeenExhausted to throw when there are no registered actions, as the check would be a bit more complex (checking that at least one behavior has an action) 🤔
There was a problem hiding this comment.
I think throwing if there are no registered behaviours makes sense
| /** | ||
| * Defines behavior for a specific set of arguments. | ||
| * | ||
| * Multiple behaviors can be stacked for the same arguments. They are matched last-registered first (LIFO stack, last in first out), so earlier entries act as fallbacks once later ones are exhausted. |
There was a problem hiding this comment.
The doc example doesn't read well to me, so I want FIFO.
* @example
* // Stack behaviors: first call returns `false`, subsequent calls return `true`
* vi.when(spy)
* .calledWith('darkMode')
* .thenReturn(true)
* .thenReturnOnce(false)| const diagnostics = w._getDiagnostics() | ||
|
|
||
| if (diagnostics.isExhausted === isNot) { | ||
| throw new chai.AssertionError(isNot |
There was a problem hiding this comment.
I don't think you even need to care about negate if you use this.assert here instead of throwing an error
|
I think I fixed all the review comments. I fixed another edge-case I found with Hiroshi's incomplete chain example and added a couple of test cases, so hopefully the suite has enough coverage and will be able to catch regressions in the future. |
|
It would be nice to have a new recipe now after the #10226 was merged. See #10381 for follow up, and https://main.vitest.dev/guide/recipes/db-transaction.html for example Just a page explaining the use case of this feature |
There was a problem hiding this comment.
I have to triple check this tomorrow (examples were generated, have to check for API hallucinations).
I'm not even sure if it's a recipe or just a guide at this point, but I like the structure and that it's less abstract than what I've added previously.
| IntersectionObserver === undefined | ||
| ``` | ||
|
|
||
| ### vi.when <Version>5.0</Version> {#vi-when} |
There was a problem hiding this comment.
| ### vi.when <Version>5.0</Version> {#vi-when} | |
| ### vi.when <Version>5.0.0</Version> {#vi-when} |
|
|
||
| # Conditional Mocking with `vi.when` | ||
|
|
||
| <Version>5.0.0</Version> |
There was a problem hiding this comment.
| <Version>5.0.0</Version> |
I don't think we need the version here
| }) | ||
| ``` | ||
|
|
||
| This works, but it becomes tedious because you have to write the argument-matching logic yourself. This is something that Vitest can handle for you when using the [`vi.when`](/api/vi#vi-when) API. |
There was a problem hiding this comment.
| This works, but it becomes tedious because you have to write the argument-matching logic yourself. This is something that Vitest can handle for you when using the [`vi.when`](/api/vi#vi-when) API. | |
| This works, but it becomes tedious because you have to write the argument-matching logic yourself. This is something that Vitest can handle for you when using the [`vi.when`](/api/vi#vi-when) <Version>5.0.0</Version> API. |
| title: Conditional Mocking with vi.when | Recipes | ||
| --- | ||
|
|
||
| # Conditional Mocking with `vi.when` |
There was a problem hiding this comment.
Would be nice to also mention vi.when in "mocking functions" and just link here in the Learn section too (don't explain the API there for the sake of simplicity, just have a link at the bottom, I think - or a small mention in ::: tip)
chore: remove rebase leftovers
Description
Resolves #756
Todo:
@todosPlease don't delete this checklist! Before submitting the PR, please make sure you do the following:
pnpm-lock.yamlunless you introduce a new test example.Tests
pnpm test:ci.Documentation
pnpm run docscommand.Changesets
feat:,fix:,perf:,docs:, orchore:.