As I write this in December of 2019, you may soon unintentionally join the club of those who are wondering why their tests just broke when you upgraded Vue Test Utils. Maybe this has already happened to you.
This article hopes to explain what happened, why, and how to fix your tests. You will learn three simple patterns to properly test asynchronous code. As a bonus, if you choose the async await pattern but find yourself getting “ReferenceError: regeneratorRuntime is not defined” exceptions, you’ll learn a simple fix for this problem as well.
Background
Earlier this year I purchased a copy of “Testing Vue.js Applications” by Edd Yerburgh. I wanted to become more proficient at testing Vue.js code and Edd is the authority on the subject. I highly recommend this excellent book for Vue developers at any level. A month or so later, imagine my surprise at receiving an email from the publisher including a personal note from Edd:
Thank you for purchasing Testing Vue.js Applications! I hope you’re finding it useful.
I’m emailing because I made a big mistake when I wrote the book. I didn’t version the installs of Vue Test Utils and other dependencies. This is a problem because it means the code examples won’t always work if one of the libraries makes a breaking change. This is especially relevant because Vue Test Utils is about to have a breaking change. It’s a large breaking change that removes sync mode from the library, meaning all DOM updates happen asynchronously. You can see the full details in the GitHub issue: https://github.com/vuejs/vue-test-utils/issues/1137
…
All the techniques you learn in the book will still be relevant, but you will also need to learn how to wait for asynchronous updates. I’m sorry that this has happened after the book was published.
I hope that you understand, and I’m sorry for any annoyance this will cause.
Thank you,
Edd YerburghMy team has been working on a large Vue.js application for about a year now and depend on our tests. What’s going to happen to our project? When is this going to bite and what can we do to minimize the pain?
The Problem
Vue Test Utils is the standard frontend testing framework for Vue.js apps. It is a full featured and easy to use toolkit that works so well that we don’t really have any excuses for not testing our work.
Unfortunately, Vue Test Utils as originally designed, was maybe a bit too easy to use. The authors chose a default model based on synchronous updating (sync mode) to simplify writing tests. You could write tests as if updates were instantaneous. Of course, while Vue is a fast performer, Vue doesn’t really work instantaneously.
The model of synchronous updates makes it easy to get started with testing, but has introduced problems the authors weren’t able to foresee. The authors have bravely attempted to make synchronous updates work properly but have wisely decided to address the intractable problems with synchronous updates by eliminating them. Better now than never and sooner than later.
The Solution
Vue Test Utils has always provided the ability to test asynchronous real life code. The original way to do this uses the Vue.js function nextTick([callback, context]) which optionally takes a callback and context that runs on “the next tick.” From the docs:
Defer the callback to be executed after the next DOM update cycle. Use it immediately after you’ve changed some data to wait for the DOM update.
Using this approach to test asynchronous code requires one to initiate an action, call nextTick(callback), make assertions in the callback, and finally call the jest provided done() function to signal the test runner that all assertions have completed. Such a test looks like this:
it('fetches async when a button is clicked', done => { const wrapper = shallowMount(Foo) wrapper.find('button').trigger('click') wrapper.vm.$nextTick(() => { expect(wrapper.vm.value).toBe('value') done() })})You can refactor this test using async await to avoid the callback like this:it('fetches async when a button is clicked', async () => { const wrapper = shallowMount(Foo) wrapper.find('button').trigger('click') await wrapper.vm.$nextTick() expect.assertions(1) expect(wrapper.vm.value).toBe('value')})
This form is somewhat easier to read especially as your tests get more complex. Note the differences with the callback pattern:
no need to call done() to signify that all assertions have completed
use expect.assertions() to indicate how many assertions are expected
test code is written sequentially rather than in the callback to nextTick()
While both callback and async await work for asynchronous testing, the async await form reads more like synchronous tests. Start at the top and work your way down. Callbacks, on the other hand, are read inside out and can become a nested mess when the tests get complex.
You can also use async await to test a promise:
it('awaits promise', async () => { expect.assertions(1) let resolved = false Promise.resolve().then(() => { resolved = true }) await flushPromises() expect.assertions(1) expect(resolved).toBe(true)})
Note the use of awaiting on flushPromises() to ensure that all pending promises have resolved before calling expect. You will need this function when testing code that uses promises. If needed, install flush-promises as a dependency
$ npm i --save-dev flush-promisesand import it into your test function.// add to test fileimport flushPromises from 'flush-promises';
I prefer the clean look of the async await pattern and recommend it. Don’t be fooled by the apparent simplicity of async await. For a good intro to async await see Async functions — making promises friendly from the dev team at Google.
Note that when using the async await pattern you can have as many assertions as are needed. Just make sure that you indicate to the test runner how many assertions are expected with expect.assertions(n) and be sure to call await before the assertions. You can also use multiple awaits in the test function if needed. Be aware that if you write multiple awaits, they will execute sequentially by default. This is probably fine in your test code, but you can also use async await to execute multiple awaits concurrently if needed. See this and this for more info.
I recommend updating Vue Test Utils to 1.0.0-beta.30 or newer and refactor any broken tests using one of the patterns described above.
ReferenceError: regeneratorRuntime is not defined
If you get “ReferenceError: regeneratorRuntime is not defined” trying to use async await in your tests, you can easily fix this by adding regenerator-runtime as a dependency
$ npm i --save-dev regenerator-runtimeand importing it in your test file.// add to test fileimport ‘regenerator-runtime’;
regenerator-runtime is a standalone runtime for Regenerator-compiled generator and async functions. You may or may not need this. Add only if you are getting the exception when trying to use async await in your tests.
Current status of Vue Test Utils
The last version of Vue Test Utils to officially support synchronous updates was 1.0.0-beta.28. The current version as of December 2019 is 1.0.0-beta.30 which is also the first version to remove synchronous updates. On Feb 7, 2019, Edd Yerburgh opened issue #1137 for discussion and feedback about removing synchronous updates in Vue Test Utils.
Removing sync mode · Issue #1137 · vuejs/vue-test-utils
After searching for an alternative solution, we've decided to remove synchronous updating (sync mode) from Vue Test…
github.com
The discussion on removing synchronous updates in Vue Test Utils has been going on for nearly a year, the decision has been made to move forward, and the first release of Vue Test Utils without synchronous updates is now out. The discussion period has ended and the issue has been closed. If you currently have a major investment in tests that use the old synchronous update behavior, you can lock the version of Vue Test Utils to 1.0.0-beta.28, but if you are willing to refactor your tests, you should install 1.0.0-beta.30 or newer. Try running your tests and note which fail. Fix the failing tests using one of the patterns described above.
Conclusion
I updated Vue Test Utils to 1.0.0-beta.30 and refactored all the broken tests in our project with no major issues using the patterns described here. I was pleasantly surprised to find the update and refactor to be straight forward.
I hope you find this useful in your transition to the latest version of Vue Test Utils.