Unit Testing & E2E Testing: Jest + Cypress Combo
Unit testing and E2E testing are two core methods for ensuring frontend quality. The combination of Jest, a popular unit testing framework in the JavaScript ecosystem, and Cypress, a modern E2E testing tool, can cover the entire testing spectrum from function-level validation to user interaction.
Unit Testing: Core Capabilities of Jest
Jest has become the preferred testing framework for React and other frontend projects due to its zero-configuration setup, snapshot testing, and powerful mocking capabilities. Its core advantages include:
- Fast Feedback: Hot-reload testing via
--watch
mode - Isolated Testing: Automatically creates independent sandbox environments for each test file
- Snapshot Comparison: Version control for UI component outputs
A typical test case structure looks like this:
// utils.test.js
import { formatDate } from './dateUtils';
describe('Date Formatting Utility', () => {
test('should correctly handle ISO format dates', () => {
const input = '2023-05-15T08:30:00Z';
expect(formatDate(input)).toBe('May 15, 2023');
});
test('null values should return default placeholder', () => {
expect(formatDate(null)).toBe('--');
});
});
Example of mocking in practice:
// api.test.js
jest.mock('./apiClient');
describe('User Data Fetching', () => {
it('should handle API errors', async () => {
require('./apiClient').getUser.mockRejectedValue(new Error('Timeout'));
const { fetchUser } = require('./userService');
await expect(fetchUser(123)).rejects.toThrow('Request timeout');
});
});
E2E Testing: Cypress in Action
Cypress addresses three major pain points of traditional E2E testing tools:
- Real-time test reloading
- Automatic waiting mechanism
- Time-travel debugging
Basic test scenario example:
// login.spec.js
describe('Login Flow', () => {
beforeEach(() => {
cy.visit('/login');
});
it('should prevent login with invalid credentials', () => {
cy.get('#username').type('test@demo.com');
cy.get('#password').type('wrong123');
cy.get('form').submit();
cy.contains('.error', 'Invalid credentials').should('be.visible');
});
it('should redirect to dashboard after successful login', () => {
cy.intercept('POST', '/api/login', { fixture: 'login.success.json' });
cy.get('#username').type('admin@company.com');
cy.get('#password').type('correct-password');
cy.get('form').submit();
cy.url().should('include', '/dashboard');
});
});
Advanced feature implementation:
// network.spec.js
it('should display loading state', () => {
cy.intercept('GET', '/api/products', {
delay: 2000,
fixture: 'products.json'
}).as('getProducts');
cy.visit('/products');
cy.get('.loading-indicator').should('exist');
cy.wait('@getProducts');
cy.get('.product-list').should('have.length', 5);
});
Combined Strategy: Layered Testing System
-
Pyramid Model:
- Base: 70% Jest unit tests
- Middle: 20% integration tests
- Top: 10% Cypress E2E tests
-
CI/CD Integration:
# .github/workflows/test.yml
jobs:
test:
steps:
- run: npm test # Jest unit tests
- run: npm run cy:run -- --headless # Cypress headless mode
- Shared Logic Reuse:
// Test data shared between Jest and Cypress
// test-utils/constants.js
export const TEST_USER = {
email: 'test@cypress.com',
password: 'Test1234!'
};
// In Jest tests
import { TEST_USER } from '../test-utils/constants';
// In Cypress tests
import { TEST_USER } from '../../test-utils/constants';
Debugging Tips and Performance Optimization
Jest Debugging Solution:
// Configure launch.json in VSCode
{
"type": "node",
"request": "launch",
"name": "Debug Jest Tests",
"program": "${workspaceFolder}/node_modules/jest/bin/jest",
"args": ["--runInBand", "${fileBasename}"]
}
Cypress Performance Optimization:
// cypress.config.js
module.exports = {
e2e: {
experimentalMemoryManagement: true,
numTestsKeptInMemory: 5 // Reduce memory usage
}
}
Parallel testing configuration example:
# Split test tasks
npm run jest -- --shard=1/3
npm run jest -- --shard=2/3
npm run jest -- --shard=3/3
Common Issue Solutions
Jest and CSS Module Conflicts:
// jest.config.js
module.exports = {
moduleNameMapper: {
'\\.(css|less)$': 'identity-obj-proxy'
}
}
Cypress Element Locating Strategy:
// Avoid fragile locators
cy.get('[data-testid="submit-button"]').click(); // Better than cy.get('.btn-primary').click();
// Custom commands
Cypress.Commands.add('login', (email, password) => {
cy.get('[data-testid=email]').type(email);
cy.get('[data-testid=password]').type(password);
cy.get('[data-testid=submit]').click();
});
Test Data Management:
// fixtures/products.json
{
"list": [
{
"id": 1,
"name": "Test Product",
"price": 99.9
}
]
}
// Usage in tests
cy.intercept('GET', '/api/products', { fixture: 'products.json' });
Test Coverage and Reporting
Jest coverage configuration:
// package.json
{
"scripts": {
"test:coverage": "jest --coverage"
},
"jest": {
"collectCoverageFrom": [
"src/**/*.{js,jsx}",
"!**/node_modules/**",
"!**/vendor/**"
]
}
}
Cypress coverage integration:
// cypress.config.js
module.exports = {
env: {
codeCoverage: {
url: '/__coverage__'
}
}
}
// Plugin configuration
module.exports = (on, config) => {
require('@cypress/code-coverage/task')(on, config);
return config;
};
Merge reports example:
# Merge Jest and Cypress coverage data
nyc merge .nyc_output && nyc report --reporter=lcov
Testing Adaptation for Modern Frontend Frameworks
React component testing solution:
// Button.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';
import Button from './Button';
test('button click triggers callback', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Submit</Button>);
fireEvent.click(screen.getByText('Submit'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
Vue Composition API testing:
// useCounter.test.js
import { ref } from 'vue';
import { useCounter } from './useCounter';
test('counter should increment', () => {
const count = ref(0);
const { increment } = useCounter(count);
increment();
expect(count.value).toBe(1);
});
Next.js page testing:
// home.cy.js
describe('Home Page Test', () => {
it('should prefetch data', () => {
cy.intercept('GET', '/api/posts', { fixture: 'posts.json' });
cy.visit('/');
cy.get('[data-testid=post-list]').should('have.length', 5);
});
});
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn