Vue: Vue.nextTick() and mocha don't play nice

Created on 27 Nov 2017  路  6Comments  路  Source: vuejs/vue

Version

2.5.8

Reproduction link

https://jsbin.com/lujehap/edit?html,js,output

Steps to reproduce

Run tests in repro link :)

What is expected?

Both tests should fail with similar error messages

What is actually happening?

The first test, using setTimeout, fails as expected, but with the second test, using Vue.nextTick(), just times out, with the error disappearing.


This is happening because Vue.nextTick() apparently uses a promise internally and mocha doesn't support catching unhandled promise errors.

There is a workaround, but it isn't great:

window.addEventListener('unhandledrejection', (e) => {
  throw e;
});

That at least lets you know that there was an error, but doesn't log the correct error.

You can also wrap the contents of the test in setTimeout:

it('should catch errors from Vue.nextTick()', (done) => {
  Vue.nextTick(() => {
    setTimeout(() => {
      throw new Error('test');
      done();
    });
  });
});

That will ensure that mocha knows about them.

While this is mostly a problem with mocha, I think it is unexpected that you have to handle errors inside Vue.nextTick() as if in a promise - it doesn't look like a promise from the function!

I'm not really sure what I would suggest doing about this.

Most helpful comment

@posva This is somewhat related and deserves some explanation: nextTick only returns Promise when no callback is passed, here the Promise comes from the scheduler queueing, so no .catch is possible. Instead, the error is captured by Vue and sent to Vue.config.errorHandler, which if not set, simply re-throws the error.

So the correct way to deal with this is:

it('should catch errors from Vue.nextTick()', (done) => {
  Vue.config.errorHandler = done
  Vue.nextTick(() => {
    throw new Error('test');
  });
}).timeout(1000);

All 6 comments

Just do .catch(done); to pass the error to the done callback. But this is completely unrelated to Vue

edit: My bad, sorry. I tried to run the repro but it's missing some dependencies so I couldn't actually see it wasn't working

@posva This is somewhat related and deserves some explanation: nextTick only returns Promise when no callback is passed, here the Promise comes from the scheduler queueing, so no .catch is possible. Instead, the error is captured by Vue and sent to Vue.config.errorHandler, which if not set, simply re-throws the error.

So the correct way to deal with this is:

it('should catch errors from Vue.nextTick()', (done) => {
  Vue.config.errorHandler = done
  Vue.nextTick(() => {
    throw new Error('test');
  });
}).timeout(1000);

Sweet. I had no idea you can do that, and I had no idea that Vue.nextTick() returns a promise with no arguments (I only tried it with an argument).

I've gone with this in the end:

it('should catch errors from Vue.nextTick()', () => {
  return Vue.nextTick().then(() => {
    throw new Error('test');
  });
}).timeout(1000);

Perhaps this could use some documentation. Maybe here? Happy to send a PR.

EDIT: to clarify, all the stuff you said in your message is documented in the API docs and doesn't need improving, I just didn't know about it in the context of testing and feel it could use improvement there.

Yes, please do. Something that states that in order for Vue.nextTick to return a promise you have to call it without arguments could be nice IMO

That's actually already in the Vue API docs, but I feel that could use improvement, too - it's pretty easy to miss that you get a promise if you don't provide any arguments unless you're looking for it.

https://vuejs.org/v2/api/#Vue-nextTick

Will send PRs to both projects soonish!

Was this page helpful?
0 / 5 - 0 ratings