Vue-test-utils: Mocked $refs methods can't be spied

Created on 5 Jul 2018  路  12Comments  路  Source: vuejs/vue-test-utils

Version

1.0.0-beta.11

Reproduction link

https://jsfiddle.net/7kgkfrbu/34/

Steps to reproduce

  • Create a test
  • stub a child component replacing is methods with sinon.spy()
  • run a test that execute that method

What is expected?

That the property called of the spy exist

What is actually happening?

wrapper.vm.$refs.myRefs.spyMethod.called is undefined

question

Most helpful comment

Under the hood, Vue binds all method functions to the component. This means the function that's added to the vm is a new function with a different reference to the original function in the methods object.

You can solve your problem by keeping a reference to the spy, and checking that it was called:

const mock = jasmine.createSpy()

const StubbedComponent = {
  render() {},
  methods: {
    mock
  }
}

const wrapper = shallowMount(TestComponent, {
  stubs: { StubbedComponent } 
})

wrapper.vm.$refs.stubbedComponent.mock()

expect(mock).toHaveBeenCalled()

All 12 comments

I can confirm this behaviour using the latest version v1.0.0-beta.20 and a Jasmine spy. The error is similar: Expected a spy, but got Function.

Some debugging later I can at least say that the spy is called. So the method wrapper.vm.$refs.myRefs.spyMethod must somehow wrap that spy.

I can't reproduce this. Can you provide a runnable execution in a GitHub repository?

@eddyerburgh I created a branch, see https://github.com/Trainmaster/vue-sandbox/tree/mocked-refs-methods-cant-be-spied. Just run yarn && yarn test.

Under the hood, Vue binds all method functions to the component. This means the function that's added to the vm is a new function with a different reference to the original function in the methods object.

You can solve your problem by keeping a reference to the spy, and checking that it was called:

const mock = jasmine.createSpy()

const StubbedComponent = {
  render() {},
  methods: {
    mock
  }
}

const wrapper = shallowMount(TestComponent, {
  stubs: { StubbedComponent } 
})

wrapper.vm.$refs.stubbedComponent.mock()

expect(mock).toHaveBeenCalled()

@eddyerburgh Your solution feels wrong because you're using one spy for the StubbedComponent. But that component could be used multiple times (= multiple instances in $refs) in the TestComponent.

@eddyerburgh Please re-open this issue. See my comment above.

it was helpful for me.

If you have multiple refs from a v-for, you would need to implement custom logic to make sure the correct instance called the method.

@eddyerburgh In this case it's not from a v-for, it's more like:

<some-reusable-component ref="a"></some-reusable-component>
// ...
<some-reusable-component ref="b"></some-reusable-component>

I don't understand why you're closing this issue again since it's not really fixed. And can you provide an example of the custom logic you are talking about?

Something like this:

const calledBy = []

function mock() {
  calledBy.push(this)
}

const ChildComponent = {
  template: '<p @click="someMethod" />',
  methods: {
    someMethod: mock
  }
}

const TestComponent = {
  template: `
      <div>
      <child-component ref="a"/>
      <child-component ref="b"/>
      </div>`,
  components: { ChildComponent }
}

const wrapper = mount(TestComponent)

wrapper.find('p').trigger('click')

expect(calledBy[0]).toBe(wrapper.vm.$refs.a)
expect(calledBy[0]).not.toBe(wrapper.vm.$refs.b)

I closed it because this is a question, not an issue or a bug with Vue Test Utils.

I'll try that example. But I still think that this is an issue of vue test utils. In the end it should just read

expect(wrapper.vm.$refs.a.someMethod).toHaveBeenCalled();
expect(wrapper.vm.$refs.b.someMethod).not.toHaveBeenCalled();

You could also use the setMethods method to set a method on a specific instance:

const ChildComponent = {
  template: '<p @click="someMethod" />',
  methods: {
    someMethod: () => {}
  }
}

const TestComponent = {
  template: `
      <div>
      <child-component ref="a"/>
      <child-component ref="b"/>
      </div>`,
  components: { ChildComponent }
}

const wrapper = mount(TestComponent)

const mock = jest.fn()
wrapper.find(ChildComponent).setMethods({
  someMethod: mock
})

wrapper.find(ChildComponent).trigger('click')

expect(mock).toHaveBeenCalled()

What you are suggesting requires overwriting significant Vue internals. In Vue 2.x methods defined in the methods object are bound to an instance when they are added to the instance, and are no longer the stub that you original defined in the methods object.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

eddyerburgh picture eddyerburgh  路  4Comments

robcresswell picture robcresswell  路  3Comments

fungus1487 picture fungus1487  路  3Comments

matt-sanders picture matt-sanders  路  3Comments

jonyoder picture jonyoder  路  3Comments