4

I have a function in my component inside of setup():

export default defineComponent({
  setup() {
    const handleResume = async () => {
      msg.value = {}
      try {
      } catch (err) {
      }
    }
    return { handleResume }
  }
})

Now in my test, I want to create a spy function like this:

import App from '@/views/Frame'
jest.spyOn(App, 'handleResume')

But I am getting this error:

Cannot spy the handleResume property because it is not a function; undefined given instead
tony19
  • 125,647
  • 18
  • 229
  • 307
signup
  • 135
  • 1
  • 16

2 Answers2

5

This requires Vue 3.2.31 (released yesterday), which adds support for mocking Proxy methods, enabling spies on the wrapper.vm from @vue/test-utils.

You can expose methods (or other data) from setup() with the expose property from the context argument. For example, this component exposes handleResume only in tests:

<!-- MyComponent.vue -->
<script>
import { defineComponent } from 'vue'

export default defineComponent({
                   
  setup(props, { expose }) {
    const handleResume = async () => true

    if (process.env.NODE_ENV === 'test') {
        
      expose({ handleResume })
    }

    return { handleResume }
  }
})
</script>

<template>
  <button @click="handleResume">Click</button>
</template>

If you have <script setup> instead, use the defineExpose() macro:

<!-- MyComponent.vue -->
<script setup>
const handleResume = async () => true

if (process.env.NODE_ENV === 'test') {
       
  defineExpose({ handleResume })
}
</script>

Then spy on the exposed handleResume from the wrapper.vm:

// MyComponent.spec.js
import { shallowMount } from '@vue/test-utils'
import MyComponent from '@/components/MyComponent.vue'

describe('MyComponent', () => {
  it('handles resume', async () => {
    const wrapper = shallowMount(MyComponent)
                                         
    const handleResume = jest.spyOn(wrapper.vm, 'handleResume')

    await wrapper.find('button').trigger('click')
    expect(handleResume).toHaveBeenCalled()
  })
})

demo

tony19
  • 125,647
  • 18
  • 229
  • 307
  • 1
    This seems to only work if the function is used in the template. What if a function isn't used in the template? Here is your demo updated with this example, it's not passing. [Demo](https://stackblitz.com/edit/vue-spy-on-setup-methods-z1fw8r?file=package.json) – Caleb Waldner Mar 30 '22 at 16:50
  • I think the problem there is that `handleResume2` already has a closure around the inner `handleResume`, which the tests cannot have access to. – tony19 Mar 30 '22 at 17:18
  • Thanks for the reply. I guess I'm not 100% sure what you mean by that. Shouldn't we be able to spy on `handleResume`, even if it's being called within `handleResume2`? How would you test if `handleResume` got called in this scenario? If you're up for it, maybe you could update that demo with this scenario; I'm not sure how to make this work myself and I've been at it for a while. Thanks! – Caleb Waldner Mar 30 '22 at 21:46
  • 1
    @CalebWaldner Someone posted a similar quesion to yours. See [answer](https://stackoverflow.com/a/72166833/6277151). – tony19 May 09 '22 at 04:19
  • @tonly19 Rock on, that post was a huge help – Caleb Waldner May 13 '22 at 13:50
2

you have to switch setup from object to function which return object. So your setup should looks like this:

setup() {
  const handleResume = async () => {
        msg.value = {}
        try {
          
        } catch (err) {
         
        }
      }
      
  return { handleResume }
}
     

After this you have two options, you can use @vue/test-utils, mount component as wrapper in test file and you should get access to your function by wrapper.vm.handleResume. Other solution, you can export your setup to composable and import composable into test instead of mounting component.

Witold
  • 53
  • 2