在 Vue 项目中,测试组件是确保应用质量和稳定性的关键步骤。Vue Test Utils 是一个专门为 Vue.js 应用程序编写的单元测试和集成测试工具库。它提供了丰富的 API,帮助开发者模拟用户操作、查询组件和断言测试结果,从而在不需要手动操作应用程序的情况下自动化地测试 Vue 组件的行为和交互。
安装 Vue Test Utils
首先,确保你已经安装了 Vue Test Utils。根据你使用的 Vue 版本,安装相应的 Vue Test Utils 版本:
-
Vue 2:
npm install @vue/test-utils@1 --save-dev
-
Vue 3:
npm install @vue/test-utils@next --save-dev
快速上手
假设我们有一个简单的 Vue 组件 HelloWorld.vue
:
<template><div class="hello"><h1>{{ msg }}</h1></div>
</template><script>
export default {name: 'HelloWorld',props: {msg: String}
}
</script>
接下来,我们编写测试代码:
import { shallowMount } from '@vue/test-utils';
import HelloWorld from '@/components/HelloWorld.vue';describe('HelloWorld.vue', () => {it('renders props.msg when passed', () => {const msg = 'new message';const wrapper = shallowMount(HelloWorld, {props: { msg }});expect(wrapper.text()).toMatch(msg);});
});
在这个测试中,我们使用 shallowMount
渲染 HelloWorld
组件,并传递 msg
属性。然后,我们使用 expect
断言组件的文本内容是否包含传递的 msg
。
常用 API
Vue Test Utils 提供了许多有用的 API,以下是一些常用的 API:
find(selector)
: 查找匹配选择器的第一个元素。findAll(selector)
: 查找匹配选择器的所有元素。trigger(eventType, eventData)
: 触发组件的事件。setProps(props)
: 设置组件的属性。setData(data)
: 设置组件的数据。text()
: 获取组件的文本内容。html()
: 获取组件的 HTML 代码。
示例:测试 TodoList 组件
假设我们有一个 TodoList.vue
组件:
<template><div><div v-for="todo in todos" :key="todo.id" data-test="todo">{{ todo.text }}</div><form data-test="form" @submit.prevent="createTodo"><input data-test="new-todo" v-model="newTodo" /></form></div>
</template><script setup lang="ts">
import { ref } from 'vue';
const newTodo = ref('');
const todos = ref([{id: 1,text: 'Learn Vue.js 3',completed: false}
]);function createTodo() {todos.value.push({id: 2,text: newTodo.value,completed: false});newTodo.value = '';
}
</script>
接下来,我们编写测试代码:
import { shallowMount } from '@vue/test-utils';
import TodoList from '@/components/TodoList.vue';test('测试新增待办事项', async () => {const wrapper = shallowMount(TodoList);const todo = wrapper.get('[data-test="todo"]');expect(todo.text()).toBe('Learn Vue.js 3');// 模拟新增待办事项await wrapper.get('[data-test="new-todo"]').setValue('New To Do Item');await wrapper.get('[data-test="form"]').trigger('submit');// 断言新增的待办事项expect(wrapper.findAll('[data-test="todo"]')).toHaveLength(2);
});
测试快照
生成测试快照代码如下:
expect(wrapper.element).toMatchSnapshot();
配置 Jest
如果你使用的是 Vue CLI 创建的项目,默认配置的 Jest 只检查 .spec
文件。如果想要检测 .test
类型的文件,需要在 jest.config.js
中进行配置:
module.exports = {// ...testMatch: ['**/tests/**/*.[jt]s?(x)','**/?(*.)+(spec|test).[jt]s?(x)']
};
更多示例
示例一:隐藏消息组件
组件代码:
<template><div><label htmlFor="toggle">显示说明</label><input id="toggle" type="checkbox" :checked="showMessage" @click="showMessage = !showMessage" /><div id="showMessage"><slot v-if="showMessage"></slot></div></div>
</template><script setup lang="ts">
import { ref } from 'vue';
const showMessage = ref(false);
</script>
测试代码:
import { shallowMount } from '@vue/test-utils';
import HiddenMessage from '@/components/HiddenMessage.vue';test('正确的渲染出来', () => {const wrapper = shallowMount(HiddenMessage);expect(wrapper.find('label').text()).toBe('显示说明');expect(wrapper.find('input').attributes('type')).toBe('checkbox');
});test('默认不显示信息', () => {const wrapper = shallowMount(HiddenMessage);expect(wrapper.find('#showMessage').exists()).toBe(true);expect(wrapper.find('#showMessage').text()).toBe('');
});test('点击复选框之后能够显示信息', async () => {const wrapper = shallowMount(HiddenMessage, {slots: {default: '<p>这是一段说明文字</p>'}});const checkbox = wrapper.find('input');await checkbox.trigger('click');expect(wrapper.find('#showMessage').text()).toBe('这是一段说明文字');await checkbox.trigger('click');expect(wrapper.find('#showMessage').text()).toBe('');
});
示例二:登录组件
组件代码:
<template><div><form @submit.prevent="handleSubmit"><div><label for="usernameInput">Username</label><input id="usernameInput" v-model="username" /></div><div><label for="passwordInput">Password</label><input id="passwordInput" type="password" v-model="password" /></div><button type="submit">Submit{{ state.loading ? '...' : null }}</button></form><div v-if="state.error" role="alert">{{ state.error }}</div><div v-if="state.resolved" role="alert">Congrats! You're signed in!</div></div>
</template><script setup lang="ts">
import { ref } from 'vue';interface LoginState {resolved: boolean;loading: boolean;error: string | null;
}const username = ref('');
const password = ref('');
const state = ref<LoginState>({resolved: false,loading: false,error: null,
});function handleSubmit() {state.value.loading = true;state.value.resolved = false;state.value.error = null;window.fetch('/api/login', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({username: username.value,password: password.value,}),}).then((r) =>r.json().then((data) => (r.ok ? data : Promise.reject(data)))).then((user) => {state.value.loading = false;state.value.resolved = true;state.value.error = null;localStorage.setItem('token', user.token);},(error) => {state.value.loading = false;state.value.resolved = false;state.value.error = error.message;});
}
</script>
测试代码:
import { shallowMount } from '@vue/test-utils';
import Login from '@/components/Login.vue';const fakeUserResponse = { token: 'fake_user_token' };
window.fetch = jest.fn().mockResolvedValue({ok: true,json: () => Promise.resolve(fakeUserResponse),
});afterEach(() => {window.localStorage.removeItem('token');
});test('请求成功', async () => {const wrapper = shallowMount(Login);await wrapper.find('#usernameInput').setValue('xiejie');await wrapper.find('#passwordInput').setValue('123456');await wrapper.find('form').trigger('submit');await wrapper.vm.$nextTick();await new Promise((resolve) => setTimeout(resolve, 100));expect(window.localStorage.getItem('token')).toEqual(fakeUserResponse.token);expect(wrapper.find('[role="alert"]').text()).toMatch(/Congrats/i);
});test('请求失败', async () => {window.fetch = jest.fn().mockResolvedValue({ok: true,json: () =>Promise.reject({message: '服务器内部错误',}),});const wrapper = shallowMount(Login);await wrapper.find('#usernameInput').setValue('xiejie');await wrapper.find('#passwordInput').setValue('123456');await wrapper.find('form').trigger('submit');await wrapper.vm.$nextTick();await new Promise((resolve) => setTimeout(resolve, 100));expect(window.localStorage.getItem('token')).toBeNull();expect(wrapper.find('[role="alert"]').text()).toMatch('服务器内部错误');
});
总结
本节介绍了如何使用 Vue Test Utils 测试 Vue 组件。Vue Test Utils 提供了一系列强大的 API,帮助开发者模拟用户操作、查询组件和断言测试结果,从而确保 Vue 应用程序的稳定性和可靠性。通过合理的测试,可以捕获潜在的问题,提高代码质量。