Vue-test-utils: Trigger events from child vue component

Created on 4 Nov 2017  Â·  14Comments  Â·  Source: vuejs/vue-test-utils

Hi.

I would like to be able to trigger events from children components of a given parent. Check this code out:

Parent Component (Only template):
<div class="parent">
    <child class="child-component"
      @custom-event="console.log.call(null, 'Custom Event from Child Component')" 
      @click="console.log.call(null, 'Click Event from Child Component')">
    </child>

    <div class="child-div"
      @custom-event="console.log.call(null, 'Custom Event from Native DOM Element')" 
      @click="console.log.call(null, 'Click Event from Native DOM Element')" >
    </div>
</div>
Test:
test('Testing triggers', () => {
    const wrapper = mount(Parent) // Parent Vue Component

    const childComponent = wrapper.find('.child-component') // Child Vue Component
    const childDiv = wrapper.find('.child-div') // Child Native DOM Element

    childComponent.trigger('custom-event')
    childDiv.trigger('custom-event')

    childComponent.trigger('click')
    childDiv.trigger('click')
})

The log generated in console is the following:

Custom Event from Native DOM Element
Click Event from Native DOM Element

As you can see, events triggered in Vue Components are not being emitted, so the parent can't catch them and act in consequence.

Is this expected? Maybe I'm doing something wrong ?
Thanks !

Most helpful comment

You can emit from a child component by accessing the instance:

test("triggers correctly", () => {
  const wrapper = shallow(ParentComponent) 
  wrapper.find(Child).vm.$emit('custom')
  expect(wrapper.html()).toContain('Triggered!')
})

I don't think we should add a method to emit from a component that doesn't yet exist

All 14 comments

This will not work, when you do mount it will not stub the child component, but render it's actual contents, I think. Can you post child-component?

What you probably want to do is instead of find('.child-component').trigger('custom-event'), do trigger on the actual button in the child component (maybe give it an ID or something). So:

// child component
<template>
  <button id="child" @click="$emit('custom-event')">Btn</button>
</template>
// parent
<template>
  <child-component @custom-event="something" />
</template>
// Test 
const wrapper = mount(Parent)
wrapper.find('#child').trigger('click')
// this should work (or something like this)

If you post child-component, or better yet your repo, I can add some examples of how to do this (I have done it a lot in my projects, and had the same problem, but haven't got any snippet I can share at this very moment).

Will try and put a small demo repo together later today, since I remember stuggling a little to test parent/child events.

Mmmh I think I Understand...

My problem is that I'm trying to do TDD and what I was attempting to do is create a Parent component that needs to react to events emitted from a yet undefined Child Component.

I wanted to define the behaviour of the Parent before the Child.

Maybe I'm not following a good strategy to do TDD as I'm used to from PHP.

Maybe if I shallow the Parent component, the Child will be stubbed and the events will be triggered?

Thanks for your help!

It looks like you are just testing if $emit works in that case. You should be testing your own code logic, not Vue itself.

Can you post the full code of what you are trying to test? There is no point to test if a component can react to a custom event, Vue already has internal tests for that.

Check out #145 , i think it's related.

No @lmiller1990, I don't want to test if $emit works. I want to test that my ParentComponent is able to react for events emitted for a undefined ChildComponent.

The code I put before is the full code. I mean, I don't have a real ChildComponent yet. It just something that I know I would like to wrap in its own component because it has too much logic but I would like to define in the ParentComponent.spec.js the event or api that ChildComponent would expose.

Let's try again with this snippet:

ParentComponent.vue

<template>
  <div class="ParentComponent">
    <child class="child-component" @custom="onCustom"></child>

    <p v-if="customTriggered">Triggered!</p>
  </div>
</template>

<script>
export default {
    name: 'parent-component',

    data () {
        return {
            customEmitted: false
        }
    },

    methods: {
        onCustom () {
            this.customEmitted = true
        }
    }
}
</script>

ParentComponent.spec.js

describe('ParentComponent', () => {
  test("It shows 'Triggered' when Child component allows to...", () => {
    const wrapper = mount(ParentComponent) // Maybe shallow ?

    const childComponent = wrapper.find('.child-component')
    childComponent.trigger('custom')

    expect(wrapper.html()).toContain('Triggered!')
  })
})

Note: I'm trying to test the output, not the implementation details. That's why I don't want to test wrapper.emitted() or wrapper.vm.customEmitted

Of course, I could be wrong in how to do TDD in Vue Components. Maybe ParentComponent is not responsible for making assumptions about events emitted for ChildComponent. I'm just trying to figure it out and wrap my mind around testing parent-child communication.

I hope this is clearer than before.

Thanks!

Thanks for the more detailed explanation, @Aferz .

Since you haven't defined child yet, you could make a temporary element to test with. Something like Mock.vue that contains:

<template>
  <button @click="$emit('custom')"><button>
</template>

And test with this. Then you can do an $emit on the Mock.vue component, and test like that. Basically, the same as stubbing/mocking the component out.

However, even if you do that, you are still just testing Vue. If a child component exposes an event api like you said, where custom is emitted, you would still just be testing if ParentComponent calls a method when custom is emitted - of course it does, that is what Vue's API provides.

I think what you are trying to do at the moment is:

  • "when custom event is emitted from a childcomponent (which doesn't exist yet)
  • on onCustom is called in the parent
  • customEmitted becomes true
  • "Triggered" is rendered

What you _should_ be doing is just the last two points, which is the logic you wrote. You can be confident if a child emits an event, the parent will respond.

So should your unit test should look like this:

describe('ParentComponent', () => {
  test("It shows 'Triggered' when customEmitted is true", () => {
    const wrapper = shallow(ParentComponent) 

    wrapper.setData({ customEmitted: true })

    expect(wrapper.html()).toContain('Triggered!')
  })

  // or
 test("It shows 'Triggered' when onCustom is called", () => {
    const wrapper = shallow(ParentComponent) 

    wrapper.vm.onCustom()

    wrapper.vm.update() // need to call to update UI

    expect(wrapper.html()).toContain('Triggered!')
  })
})

Of course, eventually in your app you will have to test that when something (say a button) is clicked, something else in a parent component happens - this is an integration or e2e test though, since it involves multiple components interacting, which is out of the scope of vue-test-utils.

PS: your last sentence said: "I'm just trying to figure it out and wrap my mind around testing parent-child communication.". You definitely don't need to test that, but of course Vue internally _does_ have tests for this functionality, if you are really keen you can take a look at the Vue source.

Hopefully that helps. Let me know if something isn't clear or you have more questions, we are all still learning :)

@lmiller1990:

Thank you very much for your so good explanation.

You cleared my mind a bit more about this topic.

Thanks!

This topic/question is indeed very much related to #145 which I asked last week. I was also wrapping my mind around unit tests and got stuck in stuff that is already in Vue and should not be tested.

Thanks to @lmiller1990 for pointing this out and for the clarification/explanation!

And thanks to @Aferz for sharing WW's quote :-)

Sorry for coming late to the party. I get that there's no need to test that the event from the child is received by the parent but what I would like to test is not that the parent calls a method, but that it calls the correct method. Maybe I have two child components and the parent is supposed to call different methods depending on which child fires the event. That should be part of a unit test, I think, because it's logic that is inherent to the parent component.

Sorry to bring up an old issue, but I agree with @Aferz. I don't think my unit tests should be aware of potentially private functions. E.g. both of these should pass the same unit test:

<child @foo="gotFoo = true" />

// Or

<child @foo="setFoo" />

{ methods: {
  foo() {
    this.gotFoo = true;
  }
}}

Also the same applies to setValue. IMO both of these should be treated the same, but you have to unit test them differently:

<input v-model="foo" />

<my-custom-component v-model="foo" />

Although I understand the explanation of @lmiller1990, I agree with @felixlublasser and @samboylett.
When an event is emitted by a child component, I think calling the "right code" (event handler method, code in template...) is also part of the parent component's logic.

In the example above:

<template>
  <div class="ParentComponent">
    <child class="child-component" @custom="onCustom"></child>
    <p v-if="customTriggered">Triggered!</p>
  </div>
</template>

<script>
export default {
    name: 'parent-component',
    data () {
        return {
            customEmitted: false
        }
    },
    methods: {
        onCustom () {
            this.customEmitted = true
        }
    }
}
</script>

This test:

  test("It shows 'Triggered' when customEmitted is true", () => {
    const wrapper = shallow(ParentComponent) 

    wrapper.setData({ customEmitted: true })

    expect(wrapper.html()).toContain('Triggered!')
  })

doesn't mean I want to test if the v-if directive works well, but if the component's logic/output is correct. So I think it's the same reasoning with events.
Moreover since we can trigger DOM events, we have to write 2 kind of tests to test the same thing (emitted event => expected output).

Could you reconsider adding a new feature to trigger custom events?

You can emit from a child component by accessing the instance:

test("triggers correctly", () => {
  const wrapper = shallow(ParentComponent) 
  wrapper.find(Child).vm.$emit('custom')
  expect(wrapper.html()).toContain('Triggered!')
})

I don't think we should add a method to emit from a component that doesn't yet exist

@eddyerburgh Thanks for the tip!
Is it mention somewhere in the documentation? I think it may be useful.
Then I agree adding a method isn't necessary 😄

There's a small code example in the common tips page, in the testing emitting events section—https://vue-test-utils.vuejs.org/guides/#common-tips. We could also add an 'emitting an event on a child component' section. Would you like to make a PR? Perhaps we could add it to the tips section, although

I may not have time this week, but I think I can make a PR next week.

Was this page helpful?
0 / 5 - 0 ratings