Vee-validate: Validation Provider not updating during unit test (with Jest)

Created on 5 May 2020  路  8Comments  路  Source: logaretm/vee-validate

Versions

  • vee-validate: 3.3.0
  • vue: 2.6.11

Describe the bug
Testing with Jest and VeeValidate as described in the docs doesn't seem to work.
https://logaretm.github.io/vee-validate/advanced/testing.html#testing-validationobserver-debounced-state

My submit button is disabled depending on the invalid state of the ValidationObserver. This works in the app itself, but not during unit testing (with jest).

To reproduce
Steps to reproduce the behavior:

  1. Clone reproduction repo (clean vue cli project): https://github.com/datrinh/veevalidate-test-repro
  2. Install all dependencies (run yarn)
  3. Run unit test (yarn test:unit)
  4. See error

Expected behavior
Submit button should be disabled initially (empty inputs). It works when testing the app live, but it doesn't during unit testing.

Demo link
https://github.com/datrinh/veevalidate-test-repro

Desktop (please complete the following information):

  • OS: MacOS

Additional context
The linked repo has been created for reproducing the error. It's created by using vue-cli and installing the required dependencies.

馃悰 bug

Most helpful comment

Not entirely a bug, the problem is that multiple renders could be involved meaning there are multiple ticks to wait for. Not using mocked timers work because you accidentally wait for multiple ticks but that's not reliable.

The fix I have in mind is that the observer update tick should be contained within 1 promise chain, which would make flushPromsies able to wait for it without problems. Currently, it isn't contained within any and it triggers multiple chains.

All 8 comments

Thanks for reporting this, it seems to be caused by the ValidationObserver having a denounced state computation of 16ms which is necessary for performance reasons.

A workaround is to not use mocked timers, which is a lot to ask. I will see if the entire state computation could be done in a single tick to make it easier to test.

Thanks for the response. In the meantime we figured out that the mock timer wasn't working as well. For anyone with a similar problem, this is how we currently work around this:

export const flush = async () => {
  // Flush pending Vue promises
  await new Promise((resolve) => setTimeout(resolve, 0));
  // Wait a browser tick to make sure changes are applied
  return new Promise((resolve) => setTimeout(resolve, 20));
};

Call this helper function before each assertion after some form input.

Worth mentioning: The interaction mode must be set to aggressive for the tests to work. In production it is set to eager. So call setInteractionMode('aggressive') when setting up the tests, if it is not your default.

What surprises me is that according to the docs, it should have worked at some point with mocked timers? Is this a regression bug then?

Not entirely a bug, the problem is that multiple renders could be involved meaning there are multiple ticks to wait for. Not using mocked timers work because you accidentally wait for multiple ticks but that's not reliable.

The fix I have in mind is that the observer update tick should be contained within 1 promise chain, which would make flushPromsies able to wait for it without problems. Currently, it isn't contained within any and it triggers multiple chains.

Good to see this issue. I was also trying this for almost a day. I will wait for @logaretm 's fixed

@logaretm
Hey, is this planned to be fixed? Because right now it seems that there is no reliable workaround.

I also don't understand why there are multiple ticks to wait for. Only the ValidationObserver has debounced state and in the repro code there is only one ValidationObserver.

Additionally, can you explain why Jest mock timers are not working? Because I thought that if I execute jest.advanceTimersByTime(100) then the ValidationObserver should resolve its state.

@Mikilll94 It's trickier than I anticipated, I will try to explain it in my best ability. Consider this code:

// this doesn't work for the first assertion
await flushPromises();
jest.runAllTimers();
expect(submitBtn.attributes('disabled')).toBeTruthy();

// This works for the first assertion
await flushPromises();
jest.runAllTimers();
await flushPromises();
expect(submitBtn.attributes('disabled')).toBeTruthy();

This is because when the component is first rendered, that's a tick that we need to wait for (rendering and initial validation) then when validation is triggered (due to user input), that's its own tick. Then the observer computation is done which is based on timers, which then triggers a re-render which is its own tick. So flushing promises before and after running timers makes it work.

The second assertion however fails, I'm not sure why as even flushing before and after running timers doesn't work. Having said that I never found the time to sit on this issue specifically.

@logaretm
I have created a simple project which is very similar (or almost the same) like this one posted by @datrinh and in my project flushing promises before and after running timers works for both assertions. Feel free to check it 馃憤

vee-validate-test.zip

So, maybe this code

await flushPromises();
jest.runAllTimers();
await flushPromises();

should be recommended in the docs as a reliable way of writing unit tests in vee-validate?

Not sure why it works in your case, anyways I don't thik this will be truely resolved anytime soon. I added a suggestion to the docs testing section and hopefully it will take care of this.

Was this page helpful?
0 / 5 - 0 ratings