Is it possible to pass a variable from a beforeEach/beforeAll function to my tests? (Pest php)

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Is it possible to pass a variable from a beforeEach/beforeAll function to my tests? (Pest PHP)

As developers transitioning testing strategies, you often encounter subtle differences between different ecosystems. You notice that in JavaScript environments like Jest, you can initialize variables before hooks like beforeEach and have them globally available within the test scope. When moving to PHP testing frameworks like Pest or PHPUnit, this behavior seems absent, leading to confusion about how to manage setup state across tests.

The short answer is: Directly passing a variable initialized in a beforeEach closure to a subsequent test function is not the idiomatic or standard way these frameworks are designed to work. However, there are robust, well-established patterns you can use to achieve exactly what you want—managing setup data efficiently and cleanly.

Let’s dive into why this happens and explore the best practices for setting up your test data in a Laravel/Pest environment.

The PHP vs. JavaScript Testing Paradigm Shift

The difference stems from how these ecosystems handle scope and execution context. In modern JavaScript testing (like Jest), state management often leverages closure mechanisms or specific test runner APIs that facilitate initializing variables before the actual test bodies run.

In contrast, PHP's testing framework philosophy tends to favor explicit setup within the test file structure. While beforeEach is excellent for running repeated setup logic, the variables defined inside it are generally scoped to that hook unless you explicitly store them in a globally accessible context (which we will examine).

Attempting to rely on implicit variable passing often leads to brittle tests because it bypasses explicit dependency management. Instead of trying to pass a variable, the best practice is to ensure your data setup mechanism makes the necessary information readily available to any test that needs it.

The Recommended Solution: Using Factories and Explicit Setup

For scenarios involving database records, like creating a user and needing the resulting ID, the most robust pattern in Laravel testing (and thus Pest) is to rely on the factory system and ensure the data setup is self-contained within the test or group definition.

Instead of trying to pass $user from beforeEach, we should focus on making the result of the setup immediately available where needed, often by re-running the creation or storing the ID explicitly in a way that the test can consume it.

Example: The Idiomatic Pest Approach

If you need an ID for subsequent tests, define it directly within the test scope or use methods that return the necessary data.

Consider how you can set up a user and then use that setup immediately:

<?php

use App\Models\User;

test('Test that it shows finished tasks of company', function () {
    // 1. Setup: Create the user and capture the ID directly within the test scope
    $user = User::factory()->create();
    $userId = $user->id; // Capture the ID immediately

    // 2. Assertion using the captured ID
    $this->assertNotNull($userId);
    
    // Now you can use $userId in other assertions or calls
    // Example: Check related data using the ID
    $this->assertDatabaseHas('tasks', [
        'user_id' => $userId,
        'status' => 'finished'
    ]);

})->group('user_setup'); // Grouping for organization

Why This Works Better

  1. Clarity and Locality: The setup logic (User::factory()->create()) is directly visible where the data is needed. This makes tests easier to read, debug, and maintain.
  2. No State Leakage: You avoid relying on a variable being magically persisted across separate execution contexts managed by beforeEach. Each test is responsible for establishing its own required prerequisites.
  3. Laravel Alignment: This approach aligns perfectly with Laravel's focus on Eloquent models and factory usage, which is the foundation of testing within the Laravel ecosystem (referencing best practices found on laravelcompany.com regarding testing).

Advanced Technique: Using Test Hooks for Shared Data (If Necessary)

If you have a complex setup that must be repeated across many tests, you can use beforeEach to establish the state that is required by all subsequent tests. For example, if you always need a specific authenticated user logged in for every test, you define it once:

<?php

use App\Models\User;

beforeEach(function () {
    // This setup runs before *every* test in this file.
    $this->user = User::factory()->create();
});

test('Test that shows finished tasks', function () {
    // $this->user is now available directly because beforeEach set it up.
    $this->assertNotNull($this->user->id);
    
    // ... rest of the test logic using $this->user
});

test('Another test requiring a user', function () {
    // We can access the previously created user state directly.
    $this->assertDatabaseHas('tasks', [
        'user_id' => $this->user->id,
        'status' => 'finished'
    ]);
});

In this advanced pattern, instead of trying to pass a variable out of the hook, you use the $this context provided by Pest/PHPUnit to store the shared data as an instance property (e.g., $this->user). This is the preferred method for sharing state across related tests in PHP testing environments.

Conclusion

While the desire to mirror JavaScript's implicit state passing is understandable, adopting the explicit setup approach—using factories to create necessary data and leveraging the $this context within beforeEach or directly within the test body—results in more stable, readable, and maintainable tests. Focus on making each test self-sufficient by explicitly stating its data requirements rather than relying on complex cross-hook variable propagation.