Skip to content

How to unit test inner function(not return) of setup in Vue 2.7/3? #1995

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
targetlucked69 opened this issue Aug 6, 2022 · 6 comments
Closed
Labels

Comments

@targetlucked69
Copy link

Subject of the issue

Unable to mock inner function called by a returned function

Steps to reproduce

export default {
  setup() {
    function outer() {
      inner();
    }
    function inner() {
      // do something for only outer function
    }
    return { outer, inner };
  }
};
test('should whatever', async () => {
  const actionSpy = jest.spyOn(wrapper.vm, 'inner')
  wrapper.vm.outer()
  expect(actionSpy).toHaveBeenCalled()
})

Expected behaviour

It should have been called

Actual behaviour

expect(jest.fn()).toHaveBeenCalled()

Expected number of calls: >= 1
Received number of calls:    0
@devtopher
Copy link

devtopher commented Aug 12, 2022

I'm not sure if this helps or not, but I just found that declaring the spy anywhere but the top of the test before the mounting happens is the only way to get it to work accurately. Ex.

const actionSpy = jest.spyOn(component, 'inner)
const wrapper = factory() // or mount(...etc
works, but
const wrapper = factory() // or mount(...etc
const actionSpy = jest.spyOn(component, 'inner)
fails.

Again, I'm not sure if that's what's happening here in your component but thought I'd share in hopes that it helps

@targetlucked69
Copy link
Author

Hey thank you for answering @devtopher! That's an interesting find though problem is I need to pass wrapper.vm and not the component instance.

@mlbiche
Copy link

mlbiche commented Nov 21, 2022

I noticed a similar issue when migrating a component to <script setup> Composition API and Vue ^2.7. The existing test case calling wrapper.vm.componentMethod no longer works (... is not a function) as if functions defined in setup are not injected in the vm as methods.

@mlbiche
Copy link

mlbiche commented Dec 8, 2022

I noticed a similar issue when migrating a component to <script setup> Composition API and Vue ^2.7. The existing test case calling wrapper.vm.componentMethod no longer works (... is not a function) as if functions defined in setup are not injected in the vm as methods.

Hummm, indeed, even computed vales are no longer accessible from wrapper.vm.componentComputedValue

@wobsoriano
Copy link

wobsoriano commented Dec 8, 2022

Because that's not how you should be testing components. Do not test component internals.

Say you have this:

<template>
  <div>
    <div data-testid="count">Count: {{ count }}</div>
    <button data-testid="increment-button" @click="incrementCount">
      Increment
    </button>
  </div>
</template>

<script>
export default {
  data: () => ({ count: 0 }),
  methods: {
    incrementCount() {
      this.count++
    }
  }
}
</script>

Most people test it like this:

it('calls correct method on button click', () => {
  createComponent()
  jest.spyOn(wrapper.vm, 'incrementCount').mockImplementation(() => {})

  findIncrementButton().trigger('click')

  expect(wrapper.vm.incrementCount).toHaveBeenCalled()
  expect(wrapper.vm.count).toBe(1)
})

In the code above, it's spying on component methods and making sure that when the button is clicked, the incrementCount method is called. It also checks if the count data is updated.

Both of these are wrong. You are testing the framework (Vue itself).

You should instead test the component output:

const findCount = () => wrapper.find("[data-testid='count']")
const findIncrementButton = () => wrapper.find("[data-testid='increment-button']")

it('increases the count on Increment button click', async () => {
  createComponent()
  expect(findCount().text()).toBe('Count: 0')

  findIncrementButton().trigger('click')
  await nextTick()

  expect(findCount().text()).toBe('Count: 1')
})

As you can see, we are not touching the component internals. We are only testing the output.

Here's a great video about unit testing best practices with Vue.

@mlbiche
Copy link

mlbiche commented Dec 9, 2022

@wobsoriano Waouh ! Thanks for the clarification !

Both of these are wrong. You are testing the framework (Vue itself).

Thanks for confirming this feeling I had when looking for Vue test examples.

Your example makes more sense to me, and I'll move to this logic then !

@ebisbe ebisbe added the vue-2.7 label Jan 20, 2023
@ebisbe ebisbe closed this as completed Jan 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants