vue-test-utils: setData does not force a re-render of the component when using v-model, data(), and a computed property

Version

1.0.0-beta.13

Reproduction link

https://github.com/s-robertson/vtu-setdata

Steps to reproduce

Create a new Vue component with the following criteria:

  1. Have data() return an object containing an empty string, e.g. msg
  2. Have a computed property that returns a boolean depending on if msg is empty or not. E.g. hasMsg
  3. Have an <input> in the component template that uses v-model, bound to msg
  4. Have an element, such as an <h1> in the component template that uses v-if, bound to hasMsg. This element must come AFTER the <input> in the template

Now write a unit test that:

  1. Mounts the component via shallow()
  2. Calls setData and sets msg to a non-empty string
  3. Checks to see if the template contains the <h1>

Finally, run the unit test.

What is expected?

Calling setData will cause the component to re-render, making hasMsg be true and the test will pass.

What is actually happening?

Calling setData does not cause the component to re-render. The unit test cannot find the <h1> element and fails:

 FAIL  tests/unit/HelloWorld.spec.js
  HelloWorld.vue
    ✕ renders props.msg when passed (36ms)

  ● HelloWorld.vue › renders props.msg when passed

    expect(received).toBe(expected) // Object.is equality
    
    Expected value to be:
      true
    Received:
      false

       9 |     wrapper.setData({ msg });
      10 | 
    > 11 |     expect(wrapper.contains('h1')).toBe(true);
      12 |   })
      13 | });
      14 | 
      
      at Object.<anonymous> (tests/unit/HelloWorld.spec.js:11:36)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        1.062s
Ran all test suites.
 ERROR  jest exited with code 1.
error Command failed with exit code 1.

What’s interesting is that the order of the <input> and <h1> in the template matters. If the <h1> comes before the <input> the test will also pass.

You can also get the test to pass by forcefully updating the component via wrapper.vm.$forceUpdate(); after you’ve called setData.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 18 (6 by maintainers)

Most upvoted comments

@onomojo @rafaoliverce what do your tests look like? Are you using await wrapper.vm.$nextTick() in your tests? There was a large breaking change in v30 to remove sync mode. This requires you to await for Vue to finish the next event loop after each change that will affect the DOM.

Here are the current docs on it https://vue-test-utils.vuejs.org/guides/testing-async-components.html#updates-applied-by-vue

I’ve found that sometimes I have to use an async function and call await on setData and setProps

@narenderv7 Have you tried setting sync to false and using Vue.nextTick?

test('use Vue.nextTick', (done) => {
  const wrapper = mount(TestComponent, { sync: false })
  wrapper.trigger('click')
  Vue.nextTick(() => {
    expect(wrapper.text()).toBe('updated')
    done()
  })
})

@eddyerburgh

version: @vue/test-utils": "^1.0.0-beta.16"

I have a v-if pointing to a data property called errors.recordType on an element with the attribute data-test-id="record-type-error"

this fails…

const wrapper = mount(Modal)
wrapper.setData({ errors: { 'recordType': 'Error Message' } })
expect(wrapper.contains('[data-test-id="record-type-error"]')).toBe(true)

this does not…

const wrapper = mount(Modal)
wrapper.setData({ errors: { 'recordType': 'Error Message' } })
wrapper.vm.$forceUpdate()
expect(wrapper.contains('[data-test-id="record-type-error"]')).toBe(true)

Seems to not be fixed yet, unless I’m doing something wrong.

Yes, probably that’s the cause of the problems. Thanks @JessicaSachs !

This seems to have regressed starting with 1.0.0-beta.30 and still broken in 1.0.0-beta.31. 1.0.0-beta.29 seems to work fine for me though.

@maoberlehner Similarly I’m using v-if = "show" property, Initially it was set to false and I’m making it as true in a method. I’ve tried set the {show = true} using (setData) and called the particular method But got a _isDestroyed of undefined. please see the code below.

Test.vue
<template>
<div>some data</div>
<child v-if="show"></child>
</template>

<script>
export default {
   name : 'test',
   data() => {
   return {
         show: false
     }
  },
    methods: {
       toggleShow() {
          this.show = !this.show
       }
    }
}
</script>
Test.test.js
describe('Methods ', () => {
    const mountComponent = () => {
        return shallowMount(Test, { 
        stubs: {
           'child': true
        }
        })
     }
  
    it('test toggle',  done => {
       const wrapper = mountComponent()
        wrapper.setData({ show: true });
        wrapper.vm.$forceUpdate()
       wrapper.vm.toggleShow()
       expect(wrapper.vm.show).toBe(false) // should to toggle
       done()
     })
})

While running this test got following error ~~

Test.test.js › Methods › should call toggleShow

TypeError: Cannot read property '_isDestroyed' of undefined

  57 |       const wrapper = mountComponent()
> 58 |       wrapper.setData({show: true})
     |               ^
  59 |       wrapper.vm.$forceUpdate()
  60 |       wrapper.vm.toggleShow()
  61 |       expect(wrapper.vm.show).toBe(false)

  at destroy (node_modules/vue/dist/vue.runtime.common.js:4174:28)
  at invokeDestroyHook (node_modules/vue/dist/vue.runtime.common.js:5741:59)
  at removeVnodes (node_modules/vue/dist/vue.runtime.common.js:5757:11)
  at VueComponent.patch [as __patch__] (node_modules/vue/dist/vue.runtime.common.js:6170:11)
  at VueComponent.Vue._update (node_modules/vue/dist/vue.runtime.common.js:2668:19)
  at VueComponent.vm._update (node_modules/@vue/test-utils/dist/vue-test-utils.js:2095:12)
  at VueComponent.updateComponent (node_modules/vue/dist/vue.runtime.common.js:2786:10)
  at Watcher.get (node_modules/vue/dist/vue.runtime.common.js:3140:25)
  at Watcher.run (node_modules/vue/dist/vue.runtime.common.js:3217:22)
  at Watcher.update (node_modules/vue/dist/vue.runtime.common.js:3205:10)
  at VueComponent.Vue.$forceUpdate (node_modules/vue/dist/vue.runtime.common.js:2689:19)
  at updateChildComponent (node_modules/vue/dist/vue.runtime.common.js:2863:8)
  at prepatch (node_modules/vue/dist/vue.runtime.common.js:4142:5)
  at patchVnode (node_modules/vue/dist/vue.runtime.common.js:5923:7)
  at updateChildren (node_modules/vue/dist/vue.runtime.common.js:5820:9)
  at patchVnode (node_modules/vue/dist/vue.runtime.common.js:5934:29)
  at VueComponent.patch [as __patch__] (node_modules/vue/dist/vue.runtime.common.js:6094:9)
  at VueComponent.Vue._update (node_modules/vue/dist/vue.runtime.common.js:2668:19)
  at VueComponent.vm._update (node_modules/@vue/test-utils/dist/vue-test-utils.js:2095:12)
  at VueComponent.updateComponent (node_modules/vue/dist/vue.runtime.common.js:2786:10)
  at Watcher.get (node_modules/vue/dist/vue.runtime.common.js:3140:25)
  at Watcher.run (node_modules/vue/dist/vue.runtime.common.js:3217:22)
  at Watcher.update (node_modules/vue/dist/vue.runtime.common.js:3205:10)
  at Dep.notify (node_modules/vue/dist/vue.runtime.common.js:695:13)
  at Object.reactiveSetter [as show] 

(node_modules/vue/dist/vue.runtime.common.js:1012:11) at VueComponent.proxySetter [as show] (node_modules/vue/dist/vue.runtime.common.js:3298:26) at VueComponent.set [as $set] (node_modules/vue/dist/vue.runtime.common.js:1034:17) at node_modules/@vue/test-utils/dist/vue-test-utils.js:1162:10 at Array.forEach (<anonymous>) at recursivelySetData (node_modules/@vue/test-utils/dist/vue-test-utils.js:1155:21) at VueWrapper.setData (node_modules/@vue/test-utils/dist/vue-test-utils.js:1654:3) at Object.setData (test/unit/views/tdi/HFE.test.js:59:15)

I believe this has been fixed in the dev branch. The test case you provided passes.