test-utils: Bug: Spying on setup methods doesn't work as expected

Describe the bug I faced a strange issue during testing my application. Please see this example for better context. Imagine you want to test the Counter component and incrementCounter method in it. Simple test example should look like this:

test('should react on click event', async () => {
    const wrapper = shallowMount(Counter);
    const incrementCounterFn = vi.spyOn(wrapper.vm, 'incrementCounter');

    await wrapper.find('button').trigger('click');

    expect(incrementCounterFn).toHaveBeenCalled();
});

But I get a strange error:

AssertionError: expected "incrementCounter" to be called at least once

At the same time, I have one more component Conter2. It is the same component, but it uses an “old” version of setup function and tests for this component work fine. Can someone help me with this?

To Reproduce Repo example

Expected behavior Both tests should run without any errors

Related information:

  • @vue/test-utils version: 2.0.2
  • Vue version: 3.2.37
  • node version: 16.14.2
  • npm (or yarn) version: 7.17.0

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 15 (4 by maintainers)

Most upvoted comments

Yeah I think it makes more sense: you would add the parenthesis if you had a parameter anyway @click=onLogout($event). In my teams we prefer to always have the parenthesis and be consistent.

The only way we get rid of this behavior would be to change the generated code by Vue itself, but I’m not sure that’s worth it.

I don’t think this will be added as it is also not possible to spy on a function call within a js module. I would recommend you to use some real browser E2E testing (like Cypress) for such cases.

@freakzlike In our current app, we went with option 1 since we have tons of components and we don’t want to create a separate composable for each one

Of course option 1 is the best solution. Mocking something is always a risk of having wrong integrations and therefore untested use cases.

I will close this issue for now. Feel free to reopen, when one if my solution does not fit your needs

I think this is related to the fact, that the component internal instance is closed and (normally) not accessible from outside.

In your case I would:

  1. Test the real behavior (as a user), and not rely on implementation details

or if not possible/too complicated to test

  1. Use a composable and mock it (as @wobsoriano did)

IMO when it is too complicated to test, then it is no overkill to move it into another file. So the composable can be tested in isolation

The only way to get around this issue is to put that function into a composable or another file

function useCounter() {
  const count = ref(0)

  function incrementCounter() {
    count.value++
  }

  return {
    count,
    incrementCount
  }
}
const mockIncrement = vi.fn()
vi.mock('@/composables/counter', () => ({
  useCounter () {
    return {
      incrementCounter: mockIncrement,
    }
  },
}))

test('should react on click event', async () => {
    const wrapper = shallowMount(Counter)

    await wrapper.find('button').trigger('click')

    expect(mockIncrement).toHaveBeenCalled()
})

Either that or use Options API 🤣

I know it’s overkill if that function is only used in a single component.

And here’s a related issue https://github.com/vuejs/test-utils/issues/775