Jasmine 测试生命周期 #
生命周期概览 #
Jasmine 提供了四个生命周期钩子函数,用于在测试的不同阶段执行代码:
text
┌─────────────────────────────────────────────────────────────┐
│ 测试生命周期 │
├─────────────────────────────────────────────────────────────┤
│ │
│ beforeAll ──────────────────────────────────────────────▶ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ beforeEach ─▶ it ─▶ afterEach │ │
│ │ │ │ │ │ │
│ │ └──────────────┴───────────┘ │ │
│ │ (每个测试重复) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ afterAll ───────────────────────────────────────────────▶ │
│ │
└─────────────────────────────────────────────────────────────┘
四个钩子函数 #
beforeAll #
在所有测试之前执行一次:
javascript
describe('Database tests', function() {
let connection;
beforeAll(function() {
connection = createDatabaseConnection();
});
afterAll(function() {
connection.close();
});
it('should connect to database', function() {
expect(connection.isConnected()).toBe(true);
});
it('should query data', function() {
const result = connection.query('SELECT * FROM users');
expect(result).toBeDefined();
});
});
afterAll #
在所有测试之后执行一次:
javascript
describe('File operations', function() {
let tempFile;
beforeAll(function() {
tempFile = createTempFile('test.txt');
});
afterAll(function() {
deleteTempFile(tempFile);
});
it('should create file', function() {
expect(fileExists(tempFile)).toBe(true);
});
it('should write to file', function() {
writeToFile(tempFile, 'content');
expect(readFile(tempFile)).toBe('content');
});
});
beforeEach #
在每个测试之前执行:
javascript
describe('ShoppingCart', function() {
let cart;
beforeEach(function() {
cart = new ShoppingCart();
});
it('should start empty', function() {
expect(cart.items.length).toBe(0);
});
it('should add item', function() {
cart.addItem({ name: 'Product', price: 100 });
expect(cart.items.length).toBe(1);
});
it('should calculate total', function() {
cart.addItem({ price: 100 });
cart.addItem({ price: 200 });
expect(cart.total).toBe(300);
});
});
afterEach #
在每个测试之后执行:
javascript
describe('DOM manipulation', function() {
let container;
beforeEach(function() {
container = document.createElement('div');
container.id = 'test-container';
document.body.appendChild(container);
});
afterEach(function() {
document.body.removeChild(container);
});
it('should add element to container', function() {
const element = document.createElement('span');
container.appendChild(element);
expect(container.children.length).toBe(1);
});
it('should clear container', function() {
container.innerHTML = '<span>test</span>';
container.innerHTML = '';
expect(container.children.length).toBe(0);
});
});
执行顺序详解 #
基本执行顺序 #
javascript
describe('outer', function() {
beforeAll(function() {
console.log('outer beforeAll');
});
afterAll(function() {
console.log('outer afterAll');
});
beforeEach(function() {
console.log('outer beforeEach');
});
afterEach(function() {
console.log('outer afterEach');
});
it('test 1', function() {
console.log('test 1');
});
it('test 2', function() {
console.log('test 2');
});
});
// 输出顺序:
// outer beforeAll
// outer beforeEach
// test 1
// outer afterEach
// outer beforeEach
// test 2
// outer afterEach
// outer afterAll
嵌套 describe 的执行顺序 #
javascript
describe('outer', function() {
beforeAll(function() {
console.log('outer beforeAll');
});
beforeEach(function() {
console.log('outer beforeEach');
});
describe('inner', function() {
beforeAll(function() {
console.log('inner beforeAll');
});
beforeEach(function() {
console.log('inner beforeEach');
});
it('test', function() {
console.log('test');
});
afterEach(function() {
console.log('inner afterEach');
});
afterAll(function() {
console.log('inner afterAll');
});
});
afterEach(function() {
console.log('outer afterEach');
});
afterAll(function() {
console.log('outer afterAll');
});
});
// 输出顺序:
// outer beforeAll
// inner beforeAll
// outer beforeEach
// inner beforeEach
// test
// inner afterEach
// outer afterEach
// inner afterAll
// outer afterAll
执行顺序图解 #
text
outer beforeAll
│
▼
inner beforeAll
│
▼
┌───────────────────────────────────────┐
│ outer beforeEach │
│ │ │
│ ▼ │
│ inner beforeEach │
│ │ │
│ ▼ │
│ test (执行测试) │
│ │ │
│ ▼ │
│ inner afterEach │
│ │ │
│ ▼ │
│ outer afterEach │
└───────────────────────────────────────┘
│
▼
inner afterAll
│
▼
outer afterAll
实际应用场景 #
数据库连接管理 #
javascript
describe('UserRepository', function() {
let db;
let userRepository;
beforeAll(async function() {
db = await connectToDatabase('test_db');
});
afterAll(async function() {
await db.disconnect();
});
beforeEach(async function() {
await db.clear();
userRepository = new UserRepository(db);
});
describe('create', function() {
it('should create user', async function() {
const user = await userRepository.create({
name: 'John',
email: 'john@example.com'
});
expect(user.id).toBeDefined();
});
});
describe('findById', function() {
it('should find user by id', async function() {
const created = await userRepository.create({ name: 'John' });
const found = await userRepository.findById(created.id);
expect(found.name).toBe('John');
});
});
});
测试数据准备 #
javascript
describe('OrderService', function() {
let orderService;
let testOrder;
beforeEach(function() {
orderService = new OrderService();
testOrder = {
id: 'order-123',
items: [
{ productId: 'p1', quantity: 2, price: 100 },
{ productId: 'p2', quantity: 1, price: 200 }
],
status: 'pending'
};
});
it('should calculate total', function() {
const total = orderService.calculateTotal(testOrder);
expect(total).toBe(400);
});
it('should update status', function() {
orderService.updateStatus(testOrder, 'completed');
expect(testOrder.status).toBe('completed');
});
});
Mock 和 Spy 清理 #
javascript
describe('UserService', function() {
let userService;
let mockApi;
beforeEach(function() {
mockApi = jasmine.createSpyObj('Api', ['get', 'post', 'put', 'delete']);
userService = new UserService(mockApi);
});
afterEach(function() {
});
it('should fetch user', function() {
mockApi.get.and.returnValue(Promise.resolve({ id: 1, name: 'John' }));
userService.getUser(1);
expect(mockApi.get).toHaveBeenCalledWith('/users/1');
});
it('should create user', function() {
const userData = { name: 'John' };
userService.createUser(userData);
expect(mockApi.post).toHaveBeenCalledWith('/users', userData);
});
});
DOM 测试清理 #
javascript
describe('Modal component', function() {
let modal;
let modalElement;
beforeEach(function() {
modal = new Modal({
title: 'Test Modal',
content: 'Test content'
});
modalElement = modal.render();
document.body.appendChild(modalElement);
});
afterEach(function() {
modal.destroy();
if (document.body.contains(modalElement)) {
document.body.removeChild(modalElement);
}
});
it('should render modal', function() {
expect(modalElement.querySelector('.modal-title').textContent).toBe('Test Modal');
});
it('should close on button click', function() {
const closeButton = modalElement.querySelector('.close-button');
closeButton.click();
expect(modal.isOpen).toBe(false);
});
});
this 关键字的使用 #
使用 this 共享状态 #
javascript
describe('using this', function() {
beforeEach(function() {
this.user = { name: 'John', age: 30 };
});
afterEach(function() {
});
it('should have user', function() {
expect(this.user.name).toBe('John');
});
it('can modify user', function() {
this.user.age = 31;
expect(this.user.age).toBe(31);
});
it('should have fresh user', function() {
expect(this.user.age).toBe(30);
});
});
this vs 变量 #
javascript
describe('comparing approaches', function() {
// 使用变量
let userWithVariable;
beforeEach(function() {
userWithVariable = { name: 'John' };
});
// 使用 this
beforeEach(function() {
this.userWithThis = { name: 'John' };
});
it('can use variable', function() {
expect(userWithVariable.name).toBe('John');
});
it('can use this', function() {
expect(this.userWithThis.name).toBe('John');
});
});
异步钩子函数 #
使用 done 回调 #
javascript
describe('async hooks', function() {
let data;
beforeEach(function(done) {
fetchDataFromServer(function(result) {
data = result;
done();
});
});
it('should have data', function() {
expect(data).toBeDefined();
});
});
使用 async/await #
javascript
describe('async/await hooks', function() {
let db;
beforeAll(async function() {
db = await connectToDatabase();
});
afterAll(async function() {
await db.close();
});
beforeEach(async function() {
await db.clear();
});
it('should work with async', async function() {
const result = await db.query('SELECT 1');
expect(result).toBeDefined();
});
});
最佳实践 #
1. 保持钩子简洁 #
javascript
// ✅ 好的做法 - 简洁明了
beforeEach(function() {
user = new User('John');
cart = new ShoppingCart();
});
// ❌ 不好的做法 - 太多逻辑
beforeEach(function() {
user = new User('John');
user.setEmail('john@example.com');
user.setAge(30);
user.verify();
cart = new ShoppingCart();
cart.addItem({ id: 1, name: 'Product 1' });
cart.addItem({ id: 2, name: 'Product 2' });
cart.applyDiscount('SUMMER20');
});
2. 合理使用 beforeAll vs beforeEach #
javascript
// ✅ 使用 beforeAll - 资源创建成本高
describe('Database tests', function() {
let connection;
beforeAll(function() {
connection = createConnection();
});
afterAll(function() {
connection.close();
});
});
// ✅ 使用 beforeEach - 需要干净状态
describe('ShoppingCart', function() {
let cart;
beforeEach(function() {
cart = new ShoppingCart();
});
});
3. 避免测试间依赖 #
javascript
// ❌ 不好的做法 - 测试间有依赖
describe('bad example', function() {
let counter = 0;
it('test 1', function() {
counter++;
expect(counter).toBe(1);
});
it('test 2', function() {
counter++;
expect(counter).toBe(2);
});
});
// ✅ 好的做法 - 测试独立
describe('good example', function() {
let counter;
beforeEach(function() {
counter = 0;
});
it('test 1', function() {
counter++;
expect(counter).toBe(1);
});
it('test 2', function() {
counter++;
expect(counter).toBe(1);
});
});
4. 正确处理资源清理 #
javascript
describe('resource management', function() {
let resources = [];
beforeEach(function() {
resources = [];
});
afterEach(function() {
resources.forEach(function(resource) {
resource.cleanup();
});
resources = [];
});
it('should manage resources', function() {
const resource = createResource();
resources.push(resource);
expect(resource.isReady()).toBe(true);
});
});
常见陷阱 #
1. 忘记调用 done #
javascript
// ❌ 错误 - 测试会超时
beforeEach(function(done) {
asyncOperation();
});
// ✅ 正确
beforeEach(function(done) {
asyncOperation(function() {
done();
});
});
2. 异步操作未完成 #
javascript
// ❌ 可能出问题
afterEach(function() {
cleanupDatabase();
});
// ✅ 更安全
afterEach(async function() {
await cleanupDatabase();
});
3. this 作用域问题 #
javascript
describe('this scope', function() {
beforeEach(function() {
this.value = 10;
});
// ❌ 箭头函数没有 this
it('will fail with arrow function', () => {
expect(this.value).toBe(10);
});
// ✅ 使用普通函数
it('works with regular function', function() {
expect(this.value).toBe(10);
});
});
下一步 #
现在你已经掌握了 Jasmine 的测试生命周期,接下来学习 异步测试 了解如何测试异步代码!
最后更新:2026-03-28