vue-test-utils: Can't trigger input event
I’m able to trigger a click event with vue-test-utils, but can’t seem to trigger an input event.
Here’s my component:
<template>
<div class="content">
<input @input="resizeThumbs($event)" id="resize_thumbs_input" type="range" min="40" max="400" value="200">
<div v-model="thumbnails" class="img_gallery">
<div v-bind:style="{'max-width': thumbPixelWidth + 'px' }" class="thumbnail"
v-for="thumbnail in thumbnails" :key="thumbnail.id">
<img :src="thumbnail.url" class="thumb">
</div>
</div>
</div>
</template>
<script>
export default {
name: 'thumbnails',
data: function () {
return {
thumbPixelWidth: 200
}
},
computed: {
thumbnails: {
get () {
return this.$store.state.images
}
}
},
methods: {
resizeThumbs: function (event) {
this.thumbPixelWidth = event.target.value
}
}
}
</script>
Here’s my test:
import Vue from 'vue'
import Vuex from 'vuex'
import { mount, createLocalVue } from 'vue-test-utils'
import Thumbnails from '@/components/Thumbnails'
import Fixtures from '@/test/fixtures/image-collection'
const localVue = createLocalVue()
localVue.use(Vuex)
describe('Thumbnails.vue', () => {
let wrapper
let options
let actions
let state
let store
beforeEach(() => {
actions = {
resizeThumbs: jest.fn(),
}
state = {
images: Fixtures.imageCollection,
}
store = new Vuex.Store({
state,
actions
})
options = {
computed: {
thumbnails: {
get () {
return state.images
}
}
}
}
})
it('changes thumbnail size when range input changes', () => {
const wrapper = mount(Thumbnails, { options, store, localVue })
wrapper.findAll('#resize_thumbs_input').at(0).trigger('input')
expect(actions.resizeThumbs).toHaveBeenCalled()
})
})
The test fails with the following output:
● Test.vue › changes thumbnail size when range input changes
expect(jest.fn()).toHaveBeenCalled()
Expected mock function to have been called.
About this issue
- Original URL
- State: closed
- Created 7 years ago
- Comments: 31 (12 by maintainers)
Commits related to this issue
- fix: throw error if target is set in options #266 — committed to vuejs/vue-test-utils by eddyerburgh 7 years ago
trigger creates a new DOM Event, and tries to overwrite the Event properties with the properties of options. Unfortunately, target is a read only property, which leads to the confusing error.
To trigger an input with the value of 100, you need to set the value of the element you’re triggering the event on to 100:
I’m going to add a note to the docs that target is unwritable, and throw a custom error inside trigger, so that users get an improved error message.
Let me know if setting the value before triggering the event solves your problem.
@lmiller1990 Thanks for sharing it, but I still have some questions about testing with shallowMount only, maybe you can clarify it for me.
How to use
shallowMountif component I want to test doesn’t have pure inputs/buttons, but uses some wrapper components, e.g<input-wrapper v-model="someValue" />. In that case I just have to use mount to reach the input.I’d be happy to work with component-wrapper itself instead of input. If I use shallowMount, I still can find
input-wrapperby name or importing it and passing intofindlike this:But I can’t neither trigger input on that component nor call
setValue- both don’t work. Should they?upd: this how it works for me now:
@mjvezzani I hear you loud and clear. I come from Ruby on Rails, where testing is built in and works great out of the box (mainly from many years of hard work by the community. I’m sure it was not good back in the early days, either).
This is my opinion from the last year or two of developing with Vue - take it was a grain of salt, everyone has their own style and preferences.
Regarding Vuex:
My experience regarding Vuex has been to test the two “sides” of it, if you will.
commitwithjest.fn, and assertexpect(commit).toHaveBeenCalledWith(mutationHandler, payload).Of course this means that you could break a getter, and the Vue component that relies on that getter will not fail. That’s unit tests for you, they only test things in isolation.
Not to plug my own guide, but I wrote about it in detail here (with the help of 15 contributors).
For the case of a complex form using a Vuex store and several components that compose together, I think you should test each part individually (store, mutations, validations). As for making sure it works together, it should be tested using an e2e test, using something like Selenium (meh, but what I use at work) or cypress.io (awesome).
About
shallowMountand testing around component frameworksMy personal experience reflects what you are encountering, regarding testing components composed of many other components. It’s awkward and doesn’t feel very beneficial. If possible, make your components smaller and more focused, so they are easier to test.
Some practices I have adopted:
shallowMountalmost exclusively. It’s a unit test, by usingmountit doesn’t seem like a unit test anymore. I haven’t found many use cases formount.setMethods,setDataetc). Like I demonstrated above in my snippet.triggerto test clicking buttons, then assert events are emitted, or some method is called.Case Study to illustrate these thoughts
Another case that comes up a lot is "how do I test my components that use vuetify/vuebootstrap/whatever. I don’t know what other people are doing, but in my experience if I have a component like this:
In this case, I know that
v-formworks, since Vuetify already has tests around that. That means I should not be testing it. In this case, the code I test is the validations. I would just test it like this (docs here forv-form):Let’s say
nameactually comes fromthis.$store.state.name. Test doesn’t change - the component does not need to know where thenamecomes from, only if it is valid.Writing E2E tests is time consuming but worth it
So, say we have a very complex Vuetify + Vuex form. What do I do?? Unit test the mutations and actions, and validations. Since my backend is Rails, I write a bunch of E2E tests using the testing tools there, filling out the form, submitting, and making sure that the form submission did not occur, or the correct error message is rendered, or database was correctly updated.
This take a lot longer to write, and run, that a unit test, but it is the only way I have found I can have confidence everything works together.
That was a pretty long answer, but hopefully it gives you some food for thought and keeps your motivation for Vue testing and TDD in general high! Although you can force it, I don’t think
vue-test-utilsalone is enough to fully “test” your Vue app, and is best when used to test small units of work.I dug into the bootstrap-vue project on Github and found the tests for the
b-form-inputcomponent. In those tests, they use vue-test-utils and they use thetrigger()method in their tests. The expectations in those tests are different from what I was anticipating:In this test, the expectation is on what value was emitted by the input field when an
inputevent occurs. Naively, I assumed that there was a data property in theb-form-inputcomponent definition, and that the test would assert that a value was set somewhere instead of just emitting a value. This information doesn’t really get me any closer to understanding how to get my test case working, but does shed some further context on what I am working with inb-form-input.Thanks, for a quick response… I will try your suggestion of
await wrapper.find('#input-widget').setValue('data'), and tryout the testing today.I finally settled with:
it(“should call the updateSubmitDisabled mutation”, () => { const wrapper = mount(UserForm, { localVue, store }); const input = wrapper.get(‘input[name=“name”]’); input.element.dispatchEvent(new Event(‘input’)); expect(mutations.updateSubmitDisabled).toHaveBeenCalled(); });
I think I’m passing through a similar issue by now. In my case I’m using Choices as a third party component and Jest as test runner.
Here is a JSFiddle as an example.
Here is my test:
This only test case above does not work…
For some reason, when I change the value through the
choicesInstanceit does not emits the input event only when unit testing. If I do the exact change programmatically using the browser console or interacting with Choices it works. Vue extension detects the input event correctly, but somehow this is not happening while testing with Jest.@mjvezzani great questions and observations. I like these kind of discussions in issues, as opposed to simply “how do I do X”, we should be asking “how should I do X”?
I don’t think there is any one “correct” way to test. What I’ve been doing lately is identifying all the states a component can be in (for example a form can be valid and invalid). Then I set up the component (assemble), act if necessary (click submit, for example) and assert (the form was not submitted).
In the example I provided, I used
setData. What if I forgot to writev-modelin the markup? The test would still pass based on how I wrote it. It would be giving a false positive. So I do agree with you, it would be ideal to simulate the user input in this case.Either way I would still write an e2e test using something like cypress.io, and fully exercise that entire page to have full confidence. But this is something Vue test utils should (can?) do.
@eddyerburgh I’m interested in your opinion about this topic, and is there even a way to set a
inputinside ashallowMountcomponent usingvue-test-utils?