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