-
Notifications
You must be signed in to change notification settings - Fork 233
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
Helpers: nextTick/flushPromises #137
Comments
I'm still thinking about this before I formulate my answer. My instinct is the "magic" is not ideal, unless it works 100% of the time. I'm leaning towards a combination of (2) and (3). We can keep the existing behavior (no breaking change) but include some useful tools or additional methods to make things a bit better. Like how Vue is keeping the options API, but also giving us the Composition API. I would like to explore something along the lines of I wonder if @JessicaSachs has any ideas - she did a deep dive and explored a bunch of different frameworks as part of a project at Cypress. |
I will also post this in the Testing channel in Vue land. |
For some perspective I just did something similar in testing-library and it looks like this: // wait for appearance
await waitFor(() => {
expect(getByText('lorem ipsum')).toBeInTheDocument()
})
// or even:
// Wait until the mock function has been called once.
await waitFor(() => expect(mockAPI).toHaveBeenCalledTimes(1))
// Also "findBy" queries retry until they find the element until timeout
const text = await findByText('lorem ipsum') It still somewhat leaks implementation details - I needed to know that clicking delete course would trigger an async request. I could be writing this un-idiomatically for testing-library. |
Would it make sense for the wrapper to expose an API so we can await when all promises are flushed? e.g
|
Interesting - would this basically just be something like wrapper.asyncDone = async function() {
return Promise.all([ nextTick, flushPromises ])
} Or something along these lines? Writing something like this using the plugin system would be super easy. |
I think the simplest solution without making significant changes would just be to re-export a simple Alternatively, we leave it as-is and just document it 🤷♂️ I find myself using flush-promises a lot, even in other tests frameworks. We should include this in the docs, it's mentioned here and a few other places. |
I'm in favor of reexporting given that |
Brining this up here rather than creating a new issue since this one seems relevant and seems to include the library authors. The author of the Does anyone have any thoughts about this?
|
Interesting question! Here are my two cents on this :) I've never had the need for anything else than await $nextTick or await flushPromise with vue-test-utils – yet I never used First, we must understand that flushPromise works by forcing a macrotask loop cycle that will mandatorily come after the microtask is exhausted – and promises are all executed in the microtask queue. See https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ Then in this case, seems you have to call flushPromise twice with msw because: 1) you flush a promise in msw 2) you flush your promise in mounted hook. I guess you'd have no flushPromise to do if the call to api was a function called on demand (as opposed to call made in vue lifecycle event), eg.: async fetch() {
this.status = "loading";
try {
this.blah = await axios.get(...blah...);
this.status = null;
} catch (error) {
this.status = "error";
}
}, then await fetch()
// no flushPromise required
expect(...).toBe( .... ) Given it's in created/mounted hooks, there is probably no escaping the double flushPromise with msw. And since vue lifecycle hooks are non-blocking (ie making them async don't block execution of the next life cycle step) I don't see how we could even have a waitFor function since it needs something to wait for. When mounting a vue component, I don't see how this would be possible. |
Interesting. Were you just making an assumption here or did you look at the The
This is a tangent/separate issue, but... I don't get this. To be clear, I'm referring to these features from |
Assumption, from making a lot of tests with mocks 😅 I sometimes had to call flushPromise twice for similar scenarios. Usually, I try to refactor things to avoid that. Or comment on why I can't!
I don't think it's not a coincidence 😅
Testing library have a slightly different philosophy on tests, where the focus is on the DOM (and I agree that makes lot of sense). They do wrapper for common frontend frameworks (eg.: vue, react) that will use vue-test-utils under the hood but expose a different set of tool aimed at inspecting DOM. And when you focus on DOM you don't have a choice but developing those kind utility functions. It's a close step to e2e testing but without loading all the things in a browser.
|
So... two So what is the recommended solution for this FWIW I don't really like the idea of |
Just to be clear: I'm not speaking for vue-test-utils team here! And about the philosophy: we have to keep in mind that there is no testing-library without vue-test-utils since it relies on it. It's just that testing-library went a step further than offering the essentials you need to run unit tests with vue.
Yeah, you can't do just those type of tests. I see it more as an additional tool in your unit testing tool belt. Each tools has its uses. |
Sorry, I thought you were a maintainer. That was the other person commenting above you. So I guess maybe the "solution" is to also install |
Thanks for the follow-up on this. The thing is But are there any alternatives to |
Having to call It'd be really nice for someone with expert understanding of javascript async/await/event loop (definitely not me) to chime in and concur with |
You can probably manually patch msw in your node_modules to experiment if that changes something. But I tend to agree that msw probably does something a bit extraordinary as this issue only arises with this library... |
You read my mind, was doing that as you commented. And I was able to. The local file I patched was this one: As the file is, I needed two After I patched the file to simply return I also did some conspicuous Idk man, this really seems like an edit: @kettanaito please look. I can show you the exact modifications I made to the compiled |
In my book you are absolutely right. It comes down in understanding micro vs macro tasks in JS: Goes back to:
In more precise terms, your first flushPromise flushes this Promise, and the second flushes your axios call ( EDIT: Important note though – This Promise is certainly there for a reason I can't comment on. I have not dug into that code. We'd have to explore why it's there. I know msw is popular in the world of storybook. There, from my experience with storybook, it's quite possible it is required. I've had to use setTimeout in some scenarios with storybook in the past. |
Yeah, that's the same promise I'm talking about removing if the delay is 0. If
And I'm begging Edit: That promise might just be there to support |
This promise is implemented to support custom delays. It doesn't matter if we remove it or not, the issue would still be there for people using custom delays. For consistency's sake, I'm keeping that Promise wrapper for both delayed and not delayed responses. HTTP requests as asynchronous in nature so there should be no difference whether MSW wraps them in Promises or not—you are not interacting with those promises directly. I'd love to find a proper solution for this. Managing internal library details as well as including an arbitrary number of |
@kettanaito If people are using custom delays then they are probably ready to have some kind of mechanism to wait for the delay in their tests no? But it is surprising to have to do the same when you're writing a unit test with no delay in my opinion. It looks like similar libraries like I can totally understand that you don't want to change your implementation. In that case we can amend the documentation to explain that people using msw need 2 flushPromises. |
If you're performing an asynchronous action (and HTTP request is an async action), you must await it regardless if it's delayed by third-party means. The fact that conventional mocking tools introduce a black hole in the place of the request client call, which results in an immediate resolution of any request promise, doesn't mean that's the correct approach. Skipping an event loop cycle is not the way to await asynchronous code, neither it is sensible to assume such code is going to execute at any particular cycle at all.
MSW has nothing to do with you requiring two I'm open to addressing this on the library's side if somebody is able to explain how an internal detail (a Promise we construct) is a responsibility of third-party tools. If we can actually analyze the problem and find the solution, and not a workaround, it will be published in the next release. |
I'm not speaking of behavioral differences. It's clear to me that MSW doesn't play nicely with
You must understand my stubbornness then. The code we add to the library is going to be used by hundreds of thousands of people. Yes, I'm particularly stuborn and picky when it comes to any change because I will be maintaining that change for an extended period of time. That alone is a sufficient reason to refuse any workarounds and hacks in order to satisfy particular use cases. That is not how software is built, generally. |
Maybe my comment was misleading, I was talking about our (VTU) documentation. As I said, it's ok if you have a strong opinion on how that needs to be handled in your library: the point is the setTimeout with a 0 delay triggers the need for a second flushPromises, and other libs made different choices making it unnecessary. I understand that as a maintainer you don't want to change that and introduce potential breaking changes (it's the same for in VTU). We just need to inform developers using msw with VTU that a workaround exists in our docs. If anyone wants to open a PR on VTU docs, we'll gladly review it 👍 |
How can you guarantee that there are no more pending promises on the second tick? In other words, if we introduce an extra Promise on top, now people will need three May I ask what benefit asyncCode()
await waitFor(() => uiElement) Note that this is not a matter of stylistic choice. I find the |
We don't guarantee anything: in practice, as most mocking libs have just one promise to return the result, developers rely on flushPromises, and that works fine. Same if you manually mock: you'll usually use
But most of us do not need it, as we want to mock API calls without delays and return the response directly, without having to wait for anything because that's not what we're trying to test. |
We would probably include |
Follwing up on a discussion on Discord, I wanted to open this issue to gather feedback, experiences, and opinions on how to handle async testing and the usage of
nextTick
andflushPromises
.Differences between
nextTick
andflushPromises
I couldn't explain it any better than here: testing-library/react-testing-library#11
Current state of the art
We're currently returning
nextTick
on several methods.by @cexbrayat. Turns out you might need an indeterminate number of
nextTick
calls to get everything up to date, so you end up usingflushPromises
. Happened the same to me while writing docs for HTTP requests.Also, there's this question by @JessicaSachs that is worth checking/considering:
What are the alternatives?
1. "automagically" call
flushPromises
The pros is that users have one less thing to worry about – and a thing which is quite low-level.
On the other hand, we might lose the ability to test intermediate states (such as "Loading states") before the promise resolved/rejected. If that's the case, this is a no-go.
2. Leave it as is and rely on docs
Pros is that current implementation of VTU-next would work.
Cons is that we all know that explaining something in a documentation does not translate well with "our users will know about this and will take it into account" 😅
3. Create a tool (method? helper?) to wrap
nextTick
andflushPromises
Not sure about the shape of that tool, but something along the lines of waitFor. Other alternatives mentioned on Discord are
wrapper.findAsync
.disclaimer: this issue is meant to serve as a place to discuss alternatives. After that, if we reach a consensus, I open to write a proper RFC with the suggested proposal 👍
The text was updated successfully, but these errors were encountered: