How can I test a custom input Vue component
You can do it:
- Using Vue Test Utils, and
- Mounting a parent element that uses
<currency-input>
- Fake an input event to the inner text field of
<currency-input>
with a value that it transforms (13.467
is transformed by<currency-input>
to13.46
) - Verify if, in the parent, the
price
property (bound tov-model
) has changed.
Example code (using Mocha):
import { mount } from '@vue/test-utils'import CurrencyInput from '@/components/CurrencyInput.vue'describe('CurrencyInput.vue', () => { it("changing the element's value, updates the v-model", () => { var parent = mount({ data: { price: null }, template: '<div> <currency-input v-model="price"></currency-input> </div>', components: { 'currency-input': CurrencyInput } }) var currencyInputInnerTextField = parent.find('input'); currencyInputInnerTextField.element.value = 13.467; currencyInputInnerTextField.trigger('input'); expect(parent.vm.price).toBe(13.46); });});
In-browser runnable demo using Jasmine:
var CurrencyInput = Vue.component('currency-input', { template: '\ <span>\ $\ <input\ ref="input"\ v-bind:value="value"\ v-on:input="updateValue($event.target.value)">\ </span>\ ', props: ['value'], methods: { // Instead of updating the value directly, this // method is used to format and place constraints // on the input's value updateValue: function(value) { var formattedValue = value // Remove whitespace on either side .trim() // Shorten to 2 decimal places .slice(0, value.indexOf('.') === -1 ? value.length : value.indexOf('.') + 3) // If the value was not already normalized, // manually override it to conform if (formattedValue !== value) { this.$refs.input.value = formattedValue } // Emit the number value through the input event this.$emit('input', Number(formattedValue)) } }});// specs code ///////////////////////////////////////////////////////////var mount = vueTestUtils.mount;describe('CurrencyInput', () => { it("changing the element's value, updates the v-model", () => { var parent = mount({ data() { return { price: null } }, template: '<div> <currency-input v-model="price"></currency-input> </div>', components: { 'currency-input': CurrencyInput } }); var currencyInputInnerTextField = parent.find('input'); currencyInputInnerTextField.element.value = 13.467; currencyInputInnerTextField.trigger('input'); expect(parent.vm.price).toBe(13.46); });});// load jasmine htmlReporter(function() { var env = jasmine.getEnv() env.addReporter(new jasmine.HtmlReporter()) env.execute()}())
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.css"><script src="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.js"></script><script src="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine-html.js"></script><script src="https://npmcdn.com/vue@2.5.15/dist/vue.js"></script><script src="https://cdn.jsdelivr.net/npm/vue-template-compiler@2.5.15/browser.js"></script><script src="https://rawgit.com/vuejs/vue-test-utils/2b078c68293a41d68a0a98393f497d0b0031f41a/dist/vue-test-utils.iife.js"></script>
Note: The code above works fine (as you can see), but there can be improvements to tests involving v-model
soon. Follow this issue for up-to-date info.
I would also mount a parent element that uses the component. Below a newer example with Jest and Vue Test Utils. Check the Vue documentation for more information.
import { mount } from "@vue/test-utils";import Input from "Input.vue";describe('Input.vue', () => { test('changing the input element value updates the v-model', async () => { const wrapper = mount({ data() { return { name: '' }; }, template: '<Input v-model="name" />', components: { Input }, }); const name = 'Brendan Eich'; await wrapper.find('input').setValue(name); expect(wrapper.vm.$data.name).toBe(name); }); test('changing the v-model updates the input element value', async () => { const wrapper = mount({ data() { return { name: '' }; }, template: '<Input v-model="name" />', components: { Input }, }); const name = 'Bjarne Stroustrup'; await wrapper.setData({ name }); const inputElement = wrapper.find('input').element; expect(inputElement.value).toBe(name); });});
Input.vue component:
<template> <input :value="$attrs.value" @input="$emit('input', $event.target.value)" /></template>