其他3.0
vue如何使用Jest测试
Vue + Jest 单元测试完整指南
一、环境配置
1.1 安装核心依赖
npm install --save-dev jest @vue/test-utils vue-jest babel-jest @babel/core @babel/preset-env
1.2 配置文件
jest.config.js:
module.exports = {
moduleFileExtensions: ['js', 'json', 'vue'],
transform: {
'^.+\\.js$': 'babel-jest',
'^.+\\.vue$': 'vue-jest'
},
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
},
testMatch: [
'**/tests/unit/**/*.spec.js'
],
collectCoverage: true,
coverageReporters: ['html', 'text-summary']
}
babel.config.js:
module.exports = {
presets: [
['@babel/preset-env', { targets: { node: 'current' } }]
]
}
二、组件测试基础
2.1 组件挂载测试
import { mount } from '@vue/test-utils'
import Button from '@/components/Button.vue'
describe('Button.vue', () => {
test('renders button with correct text', () => {
const wrapper = mount(Button, {
propsData: {
text: 'Click me'
}
})
expect(wrapper.text()).toContain('Click me')
expect(wrapper.classes()).toContain('btn-primary')
})
})
2.2 Props 测试
test('accepts valid props', () => {
const wrapper = mount(Button, {
propsData: {
text: 'Submit',
type: 'submit',
disabled: true
}
})
expect(wrapper.attributes('type')).toBe('submit')
expect(wrapper.attributes('disabled')).toBe('disabled')
})
三、交互测试
3.1 点击事件测试
test('emits click event when clicked', async () => {
const wrapper = mount(Button)
await wrapper.trigger('click')
expect(wrapper.emitted().click).toBeTruthy()
expect(wrapper.emitted().click.length).toBe(1)
})
3.2 表单输入测试
test('updates v-model on input', async () => {
const wrapper = mount(InputField, {
propsData: {
value: 'initial'
}
})
const input = wrapper.find('input')
await input.setValue('new value')
expect(wrapper.emitted().input[0]).toEqual(['new value'])
})
四、异步行为测试
4.1 API调用测试
import axios from 'axios'
import UserList from '@/components/UserList.vue'
jest.mock('axios')
test('fetches users on mount', async () => {
const users = [{ id: 1, name: 'John' }]
axios.get.mockResolvedValue({ data: users })
const wrapper = mount(UserList)
await wrapper.vm.$nextTick()
expect(axios.get).toHaveBeenCalledWith('/api/users')
expect(wrapper.vm.users).toEqual(users)
})
4.2 定时器测试
jest.useFakeTimers()
test('updates count after delay', () => {
const wrapper = mount(DelayedCounter)
wrapper.find('button').trigger('click')
jest.runAllTimers()
expect(wrapper.vm.count).toBe(1)
})
五、路由和Store测试
5.1 Vue Router测试
import { createLocalVue, mount } from '@vue/test-utils'
import VueRouter from 'vue-router'
const localVue = createLocalVue()
localVue.use(VueRouter)
test('navigates to about page', async () => {
const router = new VueRouter({ routes })
const wrapper = mount(NavBar, { localVue, router })
await wrapper.find('[data-test="about-link"]').trigger('click')
expect(router.currentRoute.path).toBe('/about')
})
5.2 Vuex测试
import { createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
const localVue = createLocalVue()
localVue.use(Vuex)
test('commits mutation on button click', () => {
const mutations = {
increment: jest.fn()
}
const store = new Vuex.Store({ mutations })
const wrapper = mount(Counter, { localVue, store })
wrapper.find('button').trigger('click')
expect(mutations.increment).toHaveBeenCalled()
})
六、快照测试
6.1 组件快照
test('matches snapshot', () => {
const wrapper = mount(MyComponent)
expect(wrapper.html()).toMatchSnapshot()
})
6.2 动态内容处理
test('matches snapshot with props', () => {
const wrapper = mount(MyComponent, {
propsData: {
title: 'Dynamic Title'
}
})
expect(wrapper.html()).toMatchSnapshot()
})
七、测试覆盖率
7.1 配置覆盖率收集
// jest.config.js
module.exports = {
collectCoverageFrom: [
'src/**/*.{js,vue}',
'!src/main.js',
'!src/router/index.js'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
}
7.2 查看报告
npm test -- --coverage
生成的报告会显示:
- 行覆盖率(Lines)
- 函数覆盖率(Functions)
- 分支覆盖率(Branches)
- 语句覆盖率(Statements)
八、高级技巧
8.1 自定义匹配器
// setupTests.js
expect.extend({
toBeBetween(received, floor, ceiling) {
const pass = received >= floor && received <= ceiling
return {
message: () => `expected ${received} ${pass ? 'not ' : ''}to be between ${floor} and ${ceiling}`,
pass
}
}
})
// 使用
test('price should be between 10 and 20', () => {
expect(15).toBeBetween(10, 20)
})
8.2 组件扩展测试
test('extends base component', () => {
const BaseComponent = {
template: '<div><slot /></div>'
}
const wrapper = mount({
extends: BaseComponent,
template: '<div>Extended</div>'
})
expect(wrapper.text()).toBe('Extended')
})
九、常见问题解决
9.1 样式导入问题
// jest.config.js
moduleNameMapper: {
'\\.(css|less|scss)$': 'identity-obj-proxy'
}
需要额外安装:
npm install --save-dev identity-obj-proxy
9.2 第三方库未转换
// jest.config.js
transformIgnorePatterns: [
'/node_modules/(?!lodash-es|other-es-lib)'
]
十、测试策略建议
测试重点:
- 组件渲染输出
- 用户交互行为
- Props验证
- 自定义事件
- 与Vuex/Router的集成
避免过度测试:
- 不测试第三方库
- 不测试实现细节
- 不追求100%覆盖率
最佳实践:
通过以上配置和方法,可以构建健壮的Vue组件测试套件。建议将测试集成到CI/CD流程中,确保每次提交都通过测试验证。对于大型项目,可采用测试金字塔策略:70%单元测试,20%集成测试,10%E2E测试。
netlify如何实现自动构建部署,原理是什么
Netlify 的自动构建部署系统基于 Git 工作流和现代 CI/CD 理念,其核心原理可分为以下几个关键部分:
一、自动化构建部署流程原理
- Git 集成触发机制
- 关键技术组件
组件 | 功能说明 |
---|---|
构建探测器 | 自动识别项目类型(React/Vue等)并选择最优构建命令 |
智能缓存系统 | 持久化缓存node_modules 等目录加速后续构建 |
原子化部署 | 先完整构建再切换流量,避免半成品发布 |
边缘网络同步 | 通过自有CDN在30秒内将资源分发到全球85+节点 |
二、具体实现机制
- 构建环境准备
容器配置示例:
# 虚拟构建环境规格
os: Ubuntu 20.04
cpu: 2 cores
memory: 4GB
storage: 10GB (临时空间)
预装软件: Node.js, Python, Ruby, Go等
依赖缓存原理:
# 缓存目录结构
~/cache/
├── node_modules/ # npm依赖
├── .cache/ # 构建工具缓存(如Webpack)
└── assets/ # 编译过的静态资源
- 构建过程优化
并行构建阶段:
关键优化技术:
- 增量构建:通过
git diff
识别变更文件 - 依赖预加载:利用
package-lock.json
哈希值匹配缓存 - 构建管道:允许自定义
build.lifecycle
hooks
- 部署过程
原子发布流程:
- 将构建产物打包为不可变的
.deploy
包 - 生成唯一部署ID(如
deploy-123
) - 同步到所有边缘节点后切换路由
零停机回滚:
# 回滚到指定版本
netlify deploy --prod --alias deploy-456
三、配置文件解析
netlify.toml
示例
[build]
command = "npm run build"
publish = "dist"
functions = "src/functions"
[build.environment]
NODE_VERSION = "16"
API_KEY = "${{ secrets.API_KEY }}"
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
- 环境变量管理
# 优先级顺序
1. UI设置的环境变量(加密存储)
2. netlify.toml中的变量
3. 构建系统默认变量(如DEPLOY_URL)
四、与其他平台的差异优势
特性 | Netlify | 传统CI/CD |
---|---|---|
配置复杂度 | 零配置自动识别 | 需要编写YAML/pipeline文件 |
部署速度 | 平均45秒完成构建+部署 | 通常2-5分钟 |
回滚机制 | 一键回退到任意历史版本 | 需要手动重新部署旧版本 |
预览环境 | 每个PR自动生成独立URL | 需要额外配置 |
成本 | 免费套餐包含100GB带宽 | 通常按构建分钟计费 |
五、高级功能原理
- 即时预览(Deploy Previews)
- 边缘函数(Edge Functions)
部署架构:
用户请求 -> 最近边缘节点 ->
├── 静态资源: 直接返回
└── 动态路由: 执行边缘函数(全球<50ms延迟)
函数示例:
// geo-redirect.js
export default async (request) => {
const country = request.headers.get('x-country')
if (country === 'JP') {
return Response.redirect('/ja', 302)
}
return new Response('Hello World')
}
六、性能数据参考
典型构建时间:
项目规模 | 冷启动构建 | 热缓存构建 |
---|---|---|
小型(1-10文件) | 90s | 25s |
中型(SPA应用) | 150s | 60s |
大型(100+文件) | 300s | 120s |
全球CDN性能:
- 首字节时间(TTFB): <100ms
- 静态资源加载: <500ms(全球平均)
- 缓存命中率: 98.5%
七、最佳实践建议
缓存策略:
toml[[headers]] for = "/assets/*" [headers.values] Cache-Control = "public, max-age=31536000, immutable"
构建优化:
bash# 跳过不必要的步骤 NETLIFY_USE_YARN=1 # 强制使用yarn加速安装
监控集成:
javascript// 注入性能监控 if (process.env.CONTEXT === 'production') { import('./analytics.js') }
Netlify 的自动化构建部署系统通过深度 Git 集成、智能缓存管理和全球边缘网络,实现了从代码提交到生产部署的无缝流水线。其核心技术优势在于将复杂的 CI/CD 流程抽象为开发者友好的简单操作,同时保持企业级的安全性和可靠性。
Netlify为什么不会处理单页面应用的非首页路由,原理是什么
Netlify 对单页面应用(SPA)非首页路由的特殊处理机制源于其静态资源托管的核心设计理念,其原理和工作方式如下:
一、问题本质:静态托管 vs 动态路由
- 传统静态网站行为
请求路径 服务器行为
/ → 返回 index.html
/about → 尝试返回 about.html
- SPA 特殊需求
所有路由路径都应返回 index.html
由前端路由库(如vue-router)处理实际导航
二、Netlify 的默认处理原理
- 静态资源匹配规则
- 精确匹配优先:当请求
/about
时:- 先查找是否存在
about.html
或about/index.html
- 若不存在 → 返回 404 错误
- 先查找是否存在
- 与 SPA 需求的冲突
三、解决方案技术实现
- 重定向规则配置
_redirects
文件方案:
/* /index.html 200
或 netlify.toml 配置:
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
- 底层工作原理
四、技术细节深度解析
- 重定向规则类型对比
状态码 | 语义 | 浏览器行为 | SPA适用性 |
---|---|---|---|
301 | 永久重定向 | 更新地址栏 | 不适用 |
302 | 临时重定向 | 更新地址栏 | 不适用 |
200 | 重写(rewrite) | 保持原URL | 完美适用 |
404 | 未找到 | 显示错误页 | 不适用 |
- 缓存行为影响
- 200重写:CDN会缓存
index.html
但保留原始URL - 无需担心:因为SPA的
index.html
通常很小(~1KB)
- 性能开销分析
方案 | 额外延迟 | CDN友好度 | 实现复杂度 |
---|---|---|---|
默认404 | 0ms | 高 | 高(需配置) |
200重写 | <1ms | 高 | 低 |
服务端渲染(SSR) | 100-300ms | 低 | 高 |
五、现代框架的自动适配
- 框架内置配置
- Create React App/Vue CLI:构建时自动生成
_redirects
文件 - Next.js/Nuxt.js:根据输出模式自动配置(静态导出时需特殊处理)
- 典型框架处理
// vue-router 示例
const router = createRouter({
history: createWebHistory(),
routes // 前端控制路由匹配
})
// 依赖Netlify返回index.html才能生效
六、高级场景解决方案
- 混合路由场景
# netlify.toml
[[redirects]]
from = "/api/*"
to = "https://api.example.com/:splat"
status = 200
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
- 自定义404页面
<!-- public/404.html -->
<script>
sessionStorage.setItem('redirect', location.pathname)
location.replace('/?redirect=' + encodeURIComponent(location.pathname))
</script>
七、为什么Netlify不默认处理SPA路由?
设计哲学:
- 保持静态托管的纯粹性
- 避免对特殊用例做假设
技术考量:
- 部分传统网站需要真实的404行为
- 允许更灵活的路由控制(如部分路由需要后端处理)
性能权衡:
- 无条件返回
index.html
会增加边缘节点计算开销 - 精确匹配静态文件更符合CDN最佳实践
- 无条件返回
最佳实践建议
始终显式配置重定向:
bash# 对于Vue/React项目 echo "/* /index.html 200" > public/_redirects
测试方法:
bashcurl -I https://yoursite.com/non-existent-route # 应返回200而非404
与History模式配合:
javascript// vue-router配置 const router = createRouter({ history: createWebHistory(), routes: [...] })
通过理解这些机制,开发者可以更好地利用Netlify部署SPA应用,同时保持对路由行为的完全控制。
如何用qiankun实现微前端项目
使用 qiankun 实现微前端的完整指南
一、基础架构搭建
1.1 主应用配置
安装依赖:
npm install qiankun -S
主应用初始化(Vue示例):
// main.js
import { registerMicroApps, start } from 'qiankun';
const apps = [
{
name: 'vue-app', // 微应用名称
entry: '//localhost:7101', // 微应用入口
container: '#subapp-container', // 容器ID
activeRule: '/vue', // 激活路由
props: { // 传递给微应用的props
authToken: 'main-app-token'
}
},
{
name: 'react-app',
entry: '//localhost:7102',
container: '#subapp-container',
activeRule: '/react',
}
];
registerMicroApps(apps);
// 启动qiankun
start({
sandbox: {
strictStyleIsolation: true // 严格的样式隔离
},
prefetch: 'all' // 预加载策略
});
1.2 微应用改造
Vue微应用配置:
// public-path.js
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
// main.js
import './public-path';
import Vue from 'vue';
import App from './App.vue';
import router from './router';
let instance = null;
function render(props = {}) {
const { container } = props;
instance = new Vue({
router,
render: h => h(App),
}).$mount(container ? container.querySelector('#app') : '#app');
}
// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
// qiankun生命周期钩子
export async function bootstrap() {
console.log('[vue] app bootstraped');
}
export async function mount(props) {
console.log('[vue] props from main framework', props);
render(props);
}
export async function unmount() {
instance.$destroy();
instance.$el.innerHTML = '';
instance = null;
}
Webpack配置调整:
// vue.config.js
module.exports = {
devServer: {
port: 7101,
headers: {
'Access-Control-Allow-Origin': '*'
}
},
configureWebpack: {
output: {
library: `vue-app`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${name}`,
}
}
}
二、核心功能实现
2.1 路由系统集成
主应用路由配置:
// 主应用router.js
const routes = [
{
path: '/vue/*',
name: 'vue-app',
component: () => import('@/components/MicroContainer.vue')
},
{
path: '/react/*',
name: 'react-app',
component: () => import('@/components/MicroContainer.vue')
}
]
微应用路由适配:
// 微应用router.js
const router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? '/vue/' : '/',
mode: 'history',
routes
})
2.2 样式隔离方案
qiankun提供的隔离方式:
start({
sandbox: {
strictStyleIsolation: true, // Shadow DOM隔离
experimentalStyleIsolation: true // 动态样式表隔离
}
})
手动处理冲突:
/* 微应用中使用scoped */
.app-container {
/* 添加应用前缀 */
}
三、高级功能实现
3.1 应用间通信
使用initGlobalState:
// 主应用初始化state
import { initGlobalState } from 'qiankun';
const actions = initGlobalState({
user: { name: 'admin' },
token: 'xxxx'
});
// 主应用监听变化
actions.onGlobalStateChange((state, prev) => {
console.log('全局状态变更:', state, prev);
});
// 微应用获取通信方法
export function mount(props) {
props.onGlobalStateChange((state, prev) => {
// 响应状态变化
});
props.setGlobalState({
token: 'new-token'
});
}
3.2 资源预加载
// 主应用启动配置
start({
prefetch: 'all' // 可选值: true | 'all' | string[] | function
});
// 手动预加载
import { prefetchApps } from 'qiankun';
prefetchApps([
{ name: 'vue-app', entry: '//localhost:7101' },
{ name: 'react-app', entry: '//localhost:7102' }
]);
四、部署方案
4.1 生产环境配置
微应用独立部署:
https://cdn.example.com/vue-app/
https://cdn.example.com/react-app/
主应用entry配置:
const apps = [
{
name: 'vue-app',
entry: process.env.NODE_ENV === 'production'
? 'https://cdn.example.com/vue-app/'
: '//localhost:7101',
// ...其他配置
}
]
4.2 Nginx反向代理
server {
listen 80;
server_name main-app.com;
location / {
root /path/to/main-app;
try_files $uri $uri/ /index.html;
}
location /vue-app/ {
proxy_pass https://cdn.example.com/vue-app/;
proxy_set_header Host $host;
}
}
五、常见问题解决方案
5.1 静态资源加载问题
解决方案:
// 微应用webpack配置
output: {
publicPath: process.env.NODE_ENV === 'production'
? 'https://cdn.example.com/vue-app/'
: '/'
}
5.2 跨域问题处理
开发环境配置:
// 微应用devServer
devServer: {
headers: {
'Access-Control-Allow-Origin': '*'
}
}
5.3 第三方脚本冲突
解决方案:
// 主应用start配置
start({
sandbox: {
excludeAssetFilter: url => {
// 排除特定脚本的沙箱隔离
return url.includes('jquery.js');
}
}
})
六、最佳实践建议
版本管理策略:
- 主应用与微应用独立发布
- 建立版本兼容矩阵
性能优化:
javascript// 按需加载微应用 registerMicroApps([...], { beforeLoad: app => { console.log('before load', app.name); return Promise.resolve(); } });
错误监控:
javascriptimport { addErrorHandler } from 'qiankun'; addErrorHandler(error => { console.error('微应用错误:', error); // 上报错误到监控系统 });
测试策略:
- 主应用集成测试
- 微应用独立测试
- 联合E2E测试
七、完整技术栈示例
组成部分 | 推荐技术方案 |
---|---|
主应用框架 | Vue 3 + Vue Router 4 |
微应用框架 | Vue 2/3, React 16+, Angular |
构建工具 | Webpack 5 + Module Federation |
CI/CD | Jenkins + Docker |
监控系统 | Sentry + ELK |
通过以上配置和方案,可以实现一个功能完善的企业级微前端架构。qiankun的核心优势在于:
- 技术栈无关:支持多种前端框架混合使用
- 样式隔离:完善的沙箱机制避免冲突
- 简单易用:API设计简洁,接入成本低
- 生产就绪:蚂蚁金服大规模验证
实际项目中建议根据团队规模和技术栈选择合适的微应用拆分粒度,通常按照业务域划分微应用边界最为合理。