使用 Jest 和 Vue Test Utils 设置测试环境
设置一个可靠的测试环境对于确保 Vue.js 应用的可靠性和可维护性至关重要。本课程将指导你配置 Jest(一个流行的 JavaScript 测试框架)和 Vue Test Utils(一个专门为测试 Vue 组件设计的库)。在本课程结束时,你将具备为你的 Vue 组件编写有效单元测试的坚实基础,涵盖属性、事件和方法等方面。这将为你后续课程中将要涵盖的高级测试技术(如模拟依赖项和端到端测试)做好准备。
安装 Jest 和 Vue Test Utils
第一步是在你的 Vue 项目中作为开发依赖项安装 Jest 和 Vue Test Utils。打开你的终端并导航到你的项目目录。然后,使用 npm 运行以下命令:
npm install --save-dev @vue/test-utils jest babel-jest @babel/preset-env
或者,如果你更喜欢使用 yarn:
yarn add --dev @vue/test-utils jest babel-jest @babel/preset-env
解释:
@vue/test-utils
: 这是官方的 Vue 测试工具库,提供在测试环境中挂载和交互 Vue 组件的实用方法。jest
: 这是一个广泛使用的 JavaScript 测试框架,提供测试运行器、断言和模拟等功能。babel-jest
: 这是一个转换器,允许 Jest 理解现代 JavaScript 语法,包括 ES 模块和 JSX。@babel/preset-env
: 一个 Babel 预设,让你能够使用最新的 JavaScript,而无需手动管理目标环境支持哪些语法转换(插件)。这确保了你的代码与你想支持的网络浏览器兼容。
配置 Babel 以用于 Jest
Jest 需要 Babel 来转译你的 Vue 组件和 JavaScript 代码。在项目的根目录中创建一个 babel.config.js
文件,并包含以下内容:
module.exports = {
presets: [
['@babel/preset-env', { targets: { node: 'current' } }]
]
}
解释:
module.exports
: 这导出了配置对象,使其对 Babel 可用。presets
: 这个数组指定要使用的 Babel 插件。@babel/preset-env
: 此预设会根据您的目标环境(在本例中为当前的 Node.js 版本)自动确定所需的转换。targets: { node: 'current' }
: 这告诉 Babel 以当前 Node.js 版本为目标,确保与 Jest 的测试环境兼容。
配置 Jest
接下来,你需要配置 Jest。在项目的根目录中创建一个 jest.config.js
文件,内容如下:
module.exports = {
moduleFileExtensions: [
'js',
'jsx',
'json',
'vue'
],
transform: {
'^.+\\.vue$': '@vue/vue3-jest',
'.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
'^.+\\.jsx?$': 'babel-jest'
},
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
},
snapshotSerializers: [
'jest-serializer-vue'
],
testMatch: [
'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
],
testEnvironment: 'jsdom',
transformIgnorePatterns: ['/node_modules/'],
};
解释:
moduleFileExtensions
: 这个数组指定 Jest 应该识别为模块的文件扩展名。transform
: 这个对象定义了 Jest 应该如何转换不同类型的文件。^.+\\.vue$
: 这个正则表达式匹配 Vue 文件。使用@vue/vue3-jest
转换器来处理这些文件。如果你在使用 Vue 2,应该使用vue-jest
代替。.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$
: 这个正则表达式匹配各种资源文件。使用jest-transform-stub
转换器将这些文件替换为空模块,因为它们实际的内容通常对单元测试无关紧要。^.+\\.jsx?$
: 此正则表达式匹配 JavaScript 和 JSX 文件。使用babel-jest
转换器处理这些文件。
moduleNameMapper
: 该对象将模块名称映射到文件路径。^@/(.*)$': '<rootDir>/src/$1'
: 这会将以@/
开头的任何模块映射到src
目录中的相应路径。这对于解析使用@
别名的导入很有用。
snapshotSerializers
: 这个数组指定要使用的快照序列化器。jest-serializer-vue
: 这个序列化器以更易读的方式格式化 Vue 组件快照。如果你使用的是 Vue 2,你应该使用jest-serializer-vue
。
testMatch
: 这个数组指定了 Jest 应该考虑为测试文件的文件。**/tests/unit/**/*.spec.(js|jsx|ts|tsx)
: 这匹配.spec.js
、.spec.jsx
、.spec.ts
或.spec.tsx
扩展名的文件,这些文件位于tests/unit
目录中。**/__tests__/*.(js|jsx|ts|tsx)
: 这匹配__tests__
目录中的文件。
testEnvironment
: 指定测试环境。jsdom
是一个 JavaScript 实现的 DOM,适用于测试与 DOM 交互的 Vue 组件。transformIgnorePatterns
: 防止 Jest 转换node_modules
中的文件。
更新 package.json
中的测试脚本
在您的 package.json
文件中添加一个测试脚本,以便轻松运行您的测试:
{
"scripts": {
"test": "jest"
}
}
现在您可以使用命令 npm test
或 yarn test
运行您的测试。
创建您的第一个测试
在项目的根目录下创建一个 tests/unit
目录。在这个目录内,创建一个名为 example.spec.js
的文件。将以下代码添加到这个文件中:
import { shallowMount } from '@vue/test-utils';
import ExampleComponent from '@/components/ExampleComponent.vue';
describe('ExampleComponent', () => {
it('renders props.msg when passed', () => {
const msg = 'Hello Jest';
const wrapper = shallowMount(ExampleComponent, {
props: { msg }
});
expect(wrapper.text()).toContain(msg);
});
});
解释:
import { shallowMount } from '@vue/test-utils'
: 从 Vue Test Utils 中导入shallowMount
函数。shallowMount
创建组件的浅层包装器,这意味着任何子组件都会被存根化。import ExampleComponent from '@/components/ExampleComponent.vue'
: 导入你想测试的组件。注意使用@
别名,该别名在jest.config.js
中配置。describe('ExampleComponent', () => { ... })
: 为ExampleComponent
定义一个测试套件。it('renders props.msg when passed', () => { ... })
: 在测试套件中定义一个单独的测试用例。const msg = 'Hello Jest'
: 定义msg
属性的值。const wrapper = shallowMount(ExampleComponent, { props: { msg } })
: 创建一个浅包装器,包装了ExampleComponent
,并带有msg
属性。expect(wrapper.text()).toContain(msg)
: 断言组件渲染的文本包含msg
属性的值。
创建示例组件
在项目的根目录下创建一个 src/components
目录。在这个目录中,创建一个名为 ExampleComponent.vue
的文件。将以下代码添加到这个文件中:
<template>
<div>
<p>{{ msg }}</p>
</div>
</template>
<script>
export default {
props: {
msg: {
type: String,
required: true
}
}
}
</script>
解释:
<template>
: 定义组件的模板,其中包含一个段落元素,用于显示msg
属性的值。<script>
: 定义组件的 JavaScript 逻辑,包括将props
选项声明为必需的字符串的msg
prop。
运行测试
现在,使用命令 npm test
或 yarn test
运行测试。你应该看到输出表明测试已通过。
为组件编写单元测试:属性、事件和方法
现在你已经设置了一个基本的测试环境,让我们来探索如何为 Vue 组件的不同方面编写单元测试,包括 props、事件和方法。
测试属性
如前例所示,您可以通过在挂载组件时使用 shallowMount
或 mount
将其传递给组件来测试属性(我们稍后会讨论两者的区别)。然后,您可以使用断言来验证组件是否正确渲染了属性。
示例:
import { shallowMount } from '@vue/test-utils';
import MyComponent from '@/components/MyComponent.vue';
describe('MyComponent', () => {
it('renders the correct title', () => {
const title = 'My Awesome Title';
const wrapper = shallowMount(MyComponent, {
props: { title }
});
expect(wrapper.find('h1').text()).toBe(title);
});
});
解释:
wrapper.find('h1')
: 在组件的渲染输出中查找h1
元素。wrapper.find('h1').text()
: 获取h1
元素的文本内容。toBe(title)
: 断言h1
元素的文本内容与预期标题相等。
测试事件
你可以测试组件在特定操作发生时是否发出正确的事件。使用 wrapper.emitted()
来检查已发出哪些事件。
示例:
import { shallowMount } from '@vue/test-utils';
import MyButton from '@/components/MyButton.vue';
describe('MyButton', () => {
it('emits a "click" event when clicked', async () => {
const wrapper = shallowMount(MyButton);
await wrapper.find('button').trigger('click');
expect(wrapper.emitted()).toHaveProperty('click');
});
it('emits the correct payload with the "click" event', async () => {
const wrapper = shallowMount(MyButton);
await wrapper.find('button').trigger('click');
const emittedEvent = wrapper.emitted('click');
expect(emittedEvent[0]).toEqual(['payload data']);
});
});
解释:
wrapper.find('button')
: 在组件的渲染输出中查找button
元素。wrapper.find('button').trigger('click')
: 在button
元素上触发点击事件。wrapper.emitted()
: 返回一个包含组件已触发所有事件的对象。toHaveProperty('click')
: 断言click
事件已被触发。wrapper.emitted('click')
: 返回一个数组,其中每个内部数组包含传递给该事件的emit
函数的参数。toEqual(['payload data'])
: 断言第一个发出的click
事件使用['payload data']
作为负载发出。
这是相应的 MyButton.vue
组件:
<template>
<button @click="handleClick">Click me</button>
</template>
<script>
export default {
methods: {
handleClick() {
this.$emit('click', 'payload data');
}
}
}
</script>
测试方法
你可以通过组件实例来访问并测试组件的方法。
示例:
import { shallowMount } from '@vue/test-utils';
import MyComponent from '@/components/MyComponent.vue';
describe('MyComponent', () => {
it('increments the count when increment method is called', async () => {
const wrapper = shallowMount(MyComponent);
const vm = wrapper.vm; // Access the Vue instance
vm.increment();
await vm.$nextTick(); // Wait for the DOM to update
expect(wrapper.find('p').text()).toBe('Count: 1');
});
});
解释:
wrapper.vm
: 访问组件的 Vue 实例。vm.increment()
: 调用组件实例上的increment
方法。await vm.$nextTick()
: 等待状态变化后 DOM 更新。这很重要,因为 Vue 异步更新 DOM。expect(wrapper.find('p').text()).toBe('Count: 1')
: 断言在调用increment
方法后,p
元素的文本内容等于预期值。
这是对应的 MyComponent.vue
组件:
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script>
export default {
data() {
return {
count: 0
};
},
methods: {
increment() {
this.count++;
}
}
}
</script>
ShallowMount 与 Mount
Vue Test Utils 提供了两种主要的挂载组件的方法:shallowMount
和 mount
。理解它们之间的区别对于编写高效和有效的测试至关重要。
shallowMount
shallowMount
创建组件的 浅 封装。这意味着仅渲染组件本身,而任何子组件将被替换为占位符。这对于隔离正在测试的组件并防止测试受子组件变化的影响很有用。shallowMount
通常比 mount
更快、更高效。
mount
mount
创建组件的 完整 包装。这意味着组件及其所有子组件都会被渲染。这对于测试组件之间的集成并验证它们能否正确协同工作非常有用。mount
通常比 shallowMount
慢且更耗费资源。
何时使用哪个:
- 使用
shallowMount
进行单元测试,以隔离单个组件。 - 使用
mount
进行集成测试,以验证组件间的交互。