如何测试自定义输入 Vue 组件
How can I test a custom input Vue component
在 Vue.js 文档中,有一个自定义输入组件的示例。我想弄清楚如何为这样的组件编写单元测试。该组件的用法如下所示
<currency-input v-model="price"></currency-input>
可以在 https://vuejs.org/v2/guide/components.html#Form-Input-Components-using-Custom-Events
找到完整的实现
文档说
So for a component to work with v-model
, it should (these can be configured in 2.2.0+):
- accept a value prop
- emit an input event with the new value
如何编写单元测试以确保我编写的此组件可以与 v-model
一起使用?理想情况下,我不想专门测试这两个条件,我想测试当组件内的值发生变化时,它也会在模型中发生变化的行为。
你可以做到:
- 使用Vue Test Utils和
- 安装一个 parent 元素 uses
<currency-input>
- 伪造一个输入事件 到
<currency-input>
的内部文本字段,并使用它转换的值(13.467
由 <currency-input>
转换至 13.46
)
- 验证 是否在父级中
price
属性(绑定到 v-model
)已更改。
示例代码(使用 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 使用 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>
注意:上面的代码运行良好(如您所见),但很快就会对涉及 v-model
的测试进行改进。关注 this issue 获取 up-to-date 信息。
我还会挂载一个使用该组件的父元素。下面是一个带有 Jest 和 Vue Test Utils 的较新示例。查看 Vue documentation 了解更多信息。
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 分量:
<template>
<input :value="$attrs.value" @input="$emit('input', $event.target.value)" />
</template>
在 Vue.js 文档中,有一个自定义输入组件的示例。我想弄清楚如何为这样的组件编写单元测试。该组件的用法如下所示
<currency-input v-model="price"></currency-input>
可以在 https://vuejs.org/v2/guide/components.html#Form-Input-Components-using-Custom-Events
找到完整的实现文档说
So for a component to work with
v-model
, it should (these can be configured in 2.2.0+):
- accept a value prop
- emit an input event with the new value
如何编写单元测试以确保我编写的此组件可以与 v-model
一起使用?理想情况下,我不想专门测试这两个条件,我想测试当组件内的值发生变化时,它也会在模型中发生变化的行为。
你可以做到:
- 使用Vue Test Utils和
- 安装一个 parent 元素 uses
<currency-input>
- 伪造一个输入事件 到
<currency-input>
的内部文本字段,并使用它转换的值(13.467
由<currency-input>
转换至13.46
) - 验证 是否在父级中
price
属性(绑定到v-model
)已更改。
示例代码(使用 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 使用 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>
注意:上面的代码运行良好(如您所见),但很快就会对涉及 v-model
的测试进行改进。关注 this issue 获取 up-to-date 信息。
我还会挂载一个使用该组件的父元素。下面是一个带有 Jest 和 Vue Test Utils 的较新示例。查看 Vue documentation 了解更多信息。
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 分量:
<template>
<input :value="$attrs.value" @input="$emit('input', $event.target.value)" />
</template>