Lifecycle Hooks
Hooks run setup and teardown code around examples, giving you control over shared state and cleanup.
beforeEach(Closure $fn)
Runs before every example in the current context:
describe(Calculator::class, function () { beforeEach(function () { $this->calculator = new Calculator(); }); it('adds', function () { expect($this->calculator->add(2, 3))->toBe(5); }); it('subtracts', function () { expect($this->calculator->subtract(5, 3))->toBe(2); }); });
afterEach(Closure $fn)
Runs after every example, regardless of pass or fail:
describe('Database', function () { afterEach(function () { $this->db->rollback(); }); it('inserts a record', function () { // ... }); });
beforeAll(Closure $fn)
Runs once when the context is first entered, before any examples execute. Use for expensive setup that can be shared:
describe('ExpensiveService', function () { beforeAll(function () { $this->service = ExpensiveService::boot(); }); it('is ready', function () { expect($this->service->isReady())->toBeTrue(); }); });
afterAll(Closure $fn)
Runs once after all examples in the context have finished:
describe('TempFiles', function () { afterAll(function () { // cleanup temp directory }); });
Nested hook inheritance
Hooks in parent contexts run for all nested examples. Inner hooks run after outer hooks:
describe('Outer', function () { beforeEach(function () { $this->log[] = 'outer'; }); context('Inner', function () { beforeEach(function () { $this->log[] = 'inner'; }); it('runs outer then inner hooks', function () { expect($this->log)->toContain('outer'); expect($this->log)->toContain('inner'); }); }); });
Execution order
For a given example, hooks execute in this order:
| Order | Hook | Scope |
|---|---|---|
| 1 | beforeAll | Once per context, on first run |
| 2 | beforeEach | Parent contexts first, then current |
| 3 | Example body | — |
| 4 | afterEach | Current context first, then parent |
| 5 | afterAll | Once per context, after last example |
let() as setup
While not strictly a hook, let() is the primary way to set up shared state. Type-hinted parameters auto-inject mocks:
describe(UserService::class, function () { let('repo', fn() => mock(UserRepository::class)); let('service', fn() => new UserService($this->repo)); it('finds users', function () { allow($this->repo->find(1))->toReturn(['name' => 'Alice']); expect($this->service->find(1)) ->toBe(['name' => 'Alice']); }); });