前端测试完全指南:从单元测试到E2E测试
大家好,我是蔓蔓。在大厂工作时,我们团队非常注重测试,这让我们的代码更加可靠。今天我来和大家分享前端测试的完整指南。
单元测试
Jest基础
javascript
// sum.js
export function sum(a, b) {
return a + b;
}
// sum.test.js
import { sum } from './sum';
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
test('adds negative numbers', () => {
expect(sum(-1, -2)).toBe(-3);
});
test('adds zero', () => {
expect(sum(0, 0)).toBe(0);
});
Mock函数
javascript
// fetchUser.js
export async function fetchUser(id) {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
// fetchUser.test.js
import { fetchUser } from './fetchUser';
jest.mock('node-fetch');
import fetch from 'node-fetch';
test('fetchUser returns user data', async () => {
const mockUser = { id: 1, name: '蔓蔓' };
fetch.mockResolvedValue({
json: () => Promise.resolve(mockUser)
});
const user = await fetchUser(1);
expect(user).toEqual(mockUser);
expect(fetch).toHaveBeenCalledWith('/api/users/1');
});
测试覆盖率
javascript
// jest.config.js
module.exports = {
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.test.{js,jsx,ts,tsx}'
]
};
集成测试
React组件测试
jsx
// Button.jsx
function Button({ onClick, children }) {
return (
<button onClick={onClick} className="btn">
{children}
</button>
);
}
// Button.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';
import Button from './Button';
test('renders button with text', () => {
render(<Button>Click me</Button>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
test('calls onClick when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
fireEvent.click(screen.getByText('Click me'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
Vue组件测试
javascript
// Button.vue
<template>
<button @click="handleClick">{{ text }}</button>
</template>
<script>
export default {
props: ['text'],
methods: {
handleClick() {
this.$emit('click');
}
}
};
</script>
// Button.spec.js
import { mount } from '@vue/test-utils';
import Button from './Button.vue';
test('renders button', () => {
const wrapper = mount(Button, {
props: { text: 'Click me' }
});
expect(wrapper.text()).toBe('Click me');
});
test('emits click event', () => {
const wrapper = mount(Button, {
props: { text: 'Click me' }
});
wrapper.trigger('click');
expect(wrapper.emitted('click')).toHaveLength(1);
});
E2E测试
Cypress基础
javascript
// app.spec.js
describe('App', () => {
beforeEach(() => {
cy.visit('/');
});
it('displays welcome message', () => {
cy.contains('Welcome');
});
it('logs in successfully', () => {
cy.get('input[name="email"]').type('test@example.com');
cy.get('input[name="password"]').type('password');
cy.get('button[type="submit"]').click();
cy.contains('Dashboard').should('be.visible');
});
it('shows error for invalid login', () => {
cy.get('input[name="email"]').type('invalid@example.com');
cy.get('input[name="password"]').type('wrong');
cy.get('button[type="submit"]').click();
cy.contains('Invalid credentials').should('be.visible');
});
});
Playwright基础
javascript
// app.spec.js
import { test, expect } from '@playwright/test';
test('has title', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle('My App');
});
test('login flow', async ({ page }) => {
await page.goto('/login');
await page.fill('input[name="email"]', 'test@example.com');
await page.fill('input[name="password"]', 'password');
await page.click('button[type="submit"]');
await expect(page.locator('text=Dashboard')).toBeVisible();
});
测试策略
javascript
const testStrategy = {
unit: {
coverage: 80,
focus: 'individual functions and components'
},
integration: {
focus: 'interactions between components'
},
e2e: {
focus: 'end-to-end user flows',
criticalPaths: ['login', 'checkout', 'search']
}
};
总结
前端测试能提升代码质量和可靠性:
- 单元测试验证单个功能
- 集成测试验证组件交互
- E2E测试验证用户流程
- 合理设置测试覆盖率目标
技术应当有温度,可靠的测试能让用户使用更安心。