vue-test-utils: "Cannot read property '$options' of undefined" when saving and manipulating data in watchers

Version

1.0.0-beta.29

Reproduction link

https://codesandbox.io/s/20yl7w8y6y

Steps to reproduce

In the Code Sandbox run the tests - they will fail. Due to the way Code Sandbox runs tests if you re-run them they’ll pass the second time. You can make them run properly again by refreshing the page or deleting a semicolon somewhere in the file.

Commenting out either line in the handler function will cause the tests to pass.

What is expected?

Inside watchers, you should be able to save the component data into a variable and then set it.

What is actually happening?

Saving component data in a variable and then setting the data inside a watcher fails tests in the latest version of vue/vue-test-utils. This has caused many tests to suddenly fail in our application.

Stack trace (from CodeSandbox):

Cannot read property '$options' of undefined

TypeError: Cannot read property '$options' of undefined
    at updateChildComponent (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:2984:8)
    at prepatch (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:4388:5)
    at patchVnode (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:6180:7)
    at VueComponent.patch [as __patch__] (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:6352:9)
    at VueComponent.Vue._update (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:2815:19)
    at VueComponent.updateComponent (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:2933:10)
    at Watcher.get (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:3330:25)
    at Watcher.run (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:3405:22)
    at flushSchedulerQueue (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:3163:13)
    at queueWatcher (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:3252:9)
    at Watcher.update (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:3395:5)
    at Dep.notify (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:740:13)
    at Object.reactiveSetter [as myData] (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:1065:11)
    at VueComponent.proxySetter [as myData] (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:3482:26)
    at VueComponent.handler (https://20yl7w8y6y.codesandbox.io/test.spec.js:21:21)
    at VueComponent.Vue.$watch (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:3797:12)
    at createWatcher (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:3754:13)
    at initWatch (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:3736:7)
    at initState (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:3499:5)
    at VueComponent.Vue._init (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:4877:5)
    at new VueComponent (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:5024:12)
    at createComponentInstanceForVnode (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:4546:10)
    at init (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:4377:45)
    at createComponent (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:5850:9)
    at createElm (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:5797:9)
    at VueComponent.patch [as __patch__] (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:6347:7)
    at VueComponent.Vue._update (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:2812:19)
    at VueComponent.updateComponent (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:2933:10)
    at Watcher.get (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:3330:25)
    at new Watcher (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:3319:12)
    at mountComponent (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:2940:3)
    at VueComponent.Vue.$mount (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:8898:10)
    at VueComponent.Vue.$mount (https://20yl7w8y6y.codesandbox.io/node_modules/vue/dist/vue.common.dev.js:11692:16)
    at mount (https://20yl7w8y6y.codesandbox.io/node_modules/@vue/test-utils/dist/vue-test-utils.js:8649:21)
    at shallowMount (https://20yl7w8y6y.codesandbox.io/node_modules/@vue/test-utils/dist/vue-test-utils.js:8677:10)
    at Object.eval (https://20yl7w8y6y.codesandbox.io/test.spec.js:34:33)
    at https://codesandbox.io/static/js/vendors~sandbox.5596247f.chunk.js:1:180690
    at new Promise (<anonymous>)
    at t.callAsyncFn (https://codesandbox.io/static/js/vendors~sandbox.5596247f.chunk.js:1:180353)
    at https://codesandbox.io/static/js/sandbox.37038f94.js:1:186582
    at g (https://codesandbox.io/static/js/common-sandbox.458a22cf.chunk.js:1:11215)
    at Generator._invoke (https://codesandbox.io/static/js/common-sandbox.458a22cf.chunk.js:1:11003)
    at Generator.e.(anonymous function) [as next] (https://codesandbox.io/static/js/common-sandbox.458a22cf.chunk.js:1:11394)
    at r (https://codesandbox.io/static/js/common-sandbox.458a22cf.chunk.js:1:702)
    at u (https://codesandbox.io/static/js/common-sandbox.458a22cf.chunk.js:1:912)
    at https://codesandbox.io/static/js/common-sandbox.458a22cf.chunk.js:1:971
    at new Promise (<anonymous>)
    at https://codesandbox.io/static/js/common-sandbox.458a22cf.chunk.js:1:853
    at https://codesandbox.io/static/js/sandbox.37038f94.js:1:186850
    at https://codesandbox.io/static/js/sandbox.37038f94.js:1:185325

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 10
  • Comments: 28 (7 by maintainers)

Commits related to this issue

Most upvoted comments

This is caused by the sync mode change in beta.29.

The workaround is to set sync to false:

shallowMount(component, { sync: true })

We may need to change the Vue Test Utils API and remove synchronous rendering entirely, which would require explicit calls to a flushPromises helper function after each change that triggers a watcher.

sync: false works. Caution that you need it on every shallowMount or mount in a single test file. I had shallowMount without sync, and then mount with sync and still threw an error at the mount instead.

Is there a clean way to ensure that every single shalllowMount calls gets passed sync: false without adding it manually?

Thanks @eddyerburgh for the quick response.

I think this is quite a serious problem - it means many people who use watchers will suddenly start seeing tests fail with this error message - and confusingly it only seems to happen when running at least two tests - the first test always seems to work no problems. When our team tried to upgrade to beta.29 today hundreds of tests began failing (we’ve had to downgrade back to beta.28). Assuming no proper fix for this is possible we’ll need to add hundreds of flushPromises() calls throughout our tests.

Will the docs be updated soon with advice about this? And is no fix where the existing API is maintained possible?

@sobolevn When we had this issue we downgraded to 1.0.0-beta.28 where this issue doesn’t seem to occur. We’re waiting for the next release when sync mode will be removed and there will be some sort of migration guide - we plan to migrate our tests out of sync mode then.

@VladZen We have over 1500 tests using sync mode so I feel your pain, but I also respect that vue-test-utils is still in beta, so it’s not like a promise was ever made that the API would remain stable. Using the library to write a lot of tests was thus always a bit of a risk. I trust that @eddyerburgh and the core team are making decisions that will make Vue and vue-test-utils the best that they can be long-term, so I’m sure this will cause some short-term pain in exchange for a stable and quality library in the long term.

Seems to be a good news: i have about 400 tests, using sync mode and now it’s gonna be removed, nice work! You know, i appreciate all you’re doing for this project, but don’t you think it’s unfair to implement such a good feature and after users make tests for their projects you say like “It’s very buggy, it will be removed” instead of “Okay, we have bugs, but let’s try to fix them”?

I’m concerned there has not been much activity since this was reported 8 months ago now. Is testing not a major concern for VueJS? From what I can see there is a lot of focus is on getting 3.0 shipped but there still isn’t a solid reliable testing option for Vue yet.

I mean I understand this is still in beta, but I also don’t believe there are any alternatives. Avoriaz is no longer in active development as I understand it.

Considering this appears to be a regression from the previous beta release, is there any reason that this change wasn’t simply reverted? Or why the proposed fix of removing the sync option and defaulting to false behaviour wasn’t implemented?

So, how can this be fixed?

  test('should not pass validation with invalid inputs', async () => {
    expect.hasAssertions()

    // make `city` and `address` empty
    const locationInvalid = deepcopy(location)
    locationInvalid.city = ''
    locationInvalid.address = ''

    await flushPromises()

    const wrapper = mount(TLocations, {
      'sync': false,
      'attachToDocument': true,
      localVue,
      'propsData': {
        'location': locationInvalid,
      },
    })

    await flushPromises()

    // assert validation result
    const formResult = await wrapper.vm.$validator.validateAll()

    await flushPromises()
    expect(formResult).toBe(false)
  })

Gives me an error:

Summary of all failing tests
 FAIL  tests/components/templates/TLocations.spec.ts (10.269s)
  ● unit tests for TLocations component › should not pass validation with invalid inputs

    TypeError: Cannot read property '$scopedSlots' of undefined

      at updateChildComponent (node_modules/vue/dist/vue.runtime.common.dev.js:4091:27)
      at prepatch (node_modules/vue/dist/vue.runtime.common.dev.js:3119:5)
      at patchVnode (node_modules/vue/dist/vue.runtime.common.dev.js:6282:7)
      at updateChildren (node_modules/vue/dist/vue.runtime.common.dev.js:6167:9)
      at patchVnode (node_modules/vue/dist/vue.runtime.common.dev.js:6293:29)
      at updateChildren (node_modules/vue/dist/vue.runtime.common.dev.js:6167:9)
      at patchVnode (node_modules/vue/dist/vue.runtime.common.dev.js:6293:29)

I am using "@vue/test-utils": "^1.0.0-beta.29",

@danielelkington Yes it’s a very serious problem—bugs with sync mode is the reason we’re still in beta.

The only solution to avoid any edge cases and maintain a synchronous API in Vue Test Utils is to maintain a sync version of Vue and fix issues like this in the core repo, but I don’t think that’s realistic.

On top of that, Vue 3 probably won’t include synchronous updating either.

If we decide to remove sync mode, I’ll update the docs, add warnings, and make the migration as smooth as possible. But I understand it’s a huge change that will affect a lot of codebases, and I’m sorry for the extra work it will cause.

@VladZen we tried to fix them with multiple approaches, and all of them led to different bugs. This is the best approach for the long-term.

Thank you @danielelkington

@AtofStryker No need to create an extra issue, since the root cause is the same.

We’ve decided to remove sync mode, I’ll write-up the reasons why in the next few days, and we’ll remove sync mode from the next Vue Test Utils version.

… I also respect that vue-test-utils is still in beta, so it’s not like a promise was ever made that the API would remain stable. …

  1. it’s been in beta since 2017
  2. beta means minor changes possible, not removing core functionality
  3. if we are to assume API will not remain stable, then why use it at all? by using a library that will change fundamentally in 2 months, then i’m knowingly creating technical debt for everyone involved in the project.

Perhaps flushPromises should be included as a method that can be imported from test utils?

Finding that when running all my mocha tests it appears that any mount call in any test files can trigger this problem. So it looks like I have to go around and add sync: false and await wrapper.vm.$nextTick() throughout all the tests.

This seems wrong to me. Do I have this right? Is this being addressed in a future version of vue-test-utils?

Nope, I have just ignored it for now. Hopefully I can ignore this case. But, this is 100% required to be fixed.

@sobolevn have you resoved ? and how ?