pestphp/pest · error · BeforeAllWithinDescribe

The [beforeAll] hook may not be used inside a [describe] blo

Error message

The [beforeAll] hook may not be used inside a [describe] block. Please move it to the top level of [%s].

What it means

beforeAll() registers a once-per-file hook on the TestSuite, but inside a describe() block Pest has no scoping for suite-level hooks, so Functions.php throws BeforeAllWithinDescribe, naming the file. The rule: beforeAll must sit at the top level of the test file. (beforeEach inside describe is fine — only the All variants are restricted.)

Source

Thrown at src/Functions.php:47

    /**
     * @template TValue
     *
     * @param  TValue|null  $value
     * @return Expectation<TValue|null>
     */
    function expect(mixed $value = null): Expectation
    {
        return new Expectation($value);
    }
}

if (! function_exists('beforeAll')) {
    function beforeAll(Closure $closure): void
    {
        if (DescribeCall::describing() !== []) {
            $filename = Backtrace::testFile();

            throw new BeforeAllWithinDescribe($filename);
        }

        TestSuite::getInstance()->beforeAll->set($closure);
    }
}

if (! function_exists('beforeEach')) {
    /**
     * @param-closure-this TestCall  $closure
     */
    function beforeEach(?Closure $closure = null): BeforeEachCall
    {
        $filename = Backtrace::testFile();

        return new BeforeEachCall(TestSuite::getInstance(), $filename, $closure);
    }
}

View on GitHub (pinned to 1af74a215c)

Solutions

  1. Move the beforeAll() call to the top level of the file (outside any describe).
  2. If the setup only serves that group, use beforeEach() inside the describe instead — it is allowed and scoped.
  3. For per-group one-time setup, restructure: split the describe into its own test file, or perform setup lazily (once-flag inside beforeEach, or memoized helper).

Example fix

// before
describe('billing', function () {
    beforeAll(fn () => $this->seed = seedDb()); // throws
    it('charges', fn () => ...);
});
// after
beforeAll(fn () => seedDb());
describe('billing', function () {
    beforeEach(fn () => cleanTables());
    it('charges', fn () => ...);
});
Defensive patterns

Strategy: validation

Validate before calling

// cheap lint before running the suite:
foreach (glob('tests/**/*Test.php') ?: [] as $file) {
    $tokens = token_get_all(file_get_contents($file));
    $depth = 0; $line = 0;
    foreach ($tokens as $i => $t) {
        $line = is_array($t) ? $t[2] : $line;
        // simplified: flag beforeAll when a describe() is currently open
    }
}

Prevention

When it happens

Trigger: Calling beforeAll(function () { ... }) anywhere inside a describe('...', function () { ... }) block; nesting beforeAll in group helpers; moving top-level hooks into a describe during a refactor to 'scope' them.

Common situations: Reorganizing a test file to group tests and dragging hooks along; assuming Jasmine/Jest semantics where beforeAll inside describe is allowed; trying to scope expensive setup to one describe block.

Related errors


AI-assisted analysis of pestphp/pest@1af74a215c (2026-08-21). Data as JSON: /api/errors/86c97338ac71296c. Report an issue: GitHub.