PHPackages                             alexskrypnyk/phpunit-helpers - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Testing &amp; Quality](/categories/testing)
4. /
5. alexskrypnyk/phpunit-helpers

ActiveLibrary[Testing &amp; Quality](/categories/testing)

alexskrypnyk/phpunit-helpers
============================

Helpers to work with PHPUnit

0.16.0(3mo ago)168.0k↑99.9%[1 issues](https://github.com/AlexSkrypnyk/phpunit-helpers/issues)6GPL-2.0-or-laterPHPPHP &gt;=8.2CI passing

Since Apr 13Pushed 1w ago1 watchersCompare

[ Source](https://github.com/AlexSkrypnyk/phpunit-helpers)[ Packagist](https://packagist.org/packages/alexskrypnyk/phpunit-helpers)[ Docs](https://github.com/alexskrypnyk/phpunit-helpers)[ GitHub Sponsors](https://github.com/alexskrypnyk)[ Patreon](https://www.patreon.com/alexskrypnyk)[ RSS](/packages/alexskrypnyk-phpunit-helpers/feed)WikiDiscussions main Synced 4d ago

READMEChangelog (10)Dependencies (26)Versions (28)Used By (6)

  ![PHPUnit Helpers logo](https://camo.githubusercontent.com/8ac7f37d06874e1ffd92b4e5d632c7157f9608df86a49b80ff34c75517263303/68747470733a2f2f706c616365686f6c642e6a702f3030303030302f6666666666662f323030783230302e706e673f746578743d504850556e69742b48656c70657273266373733d253742253232626f726465722d7261646975732532322533412532322532303130307078253232253744)

Helpers to work with PHPUnit
============================

[](#helpers-to-work-with-phpunit)

[![GitHub Issues](https://camo.githubusercontent.com/a5b6c9bb8caaf34a9b121f1d9a4e0df03a35b31a8e9274307ccf398c979cfac3/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6973737565732f416c6578536b7279706e796b2f706870756e69742d68656c706572732e737667)](https://github.com/AlexSkrypnyk/phpunit-helpers/issues)[![GitHub Pull Requests](https://camo.githubusercontent.com/ca084489ac522eb1ccd9a0daba1de140ec77ba6c5c0919b33a8d38eb0ec05f76/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6973737565732d70722f416c6578536b7279706e796b2f706870756e69742d68656c706572732e737667)](https://github.com/AlexSkrypnyk/phpunit-helpers/pulls)[![Test PHP](https://github.com/AlexSkrypnyk/phpunit-helpers/actions/workflows/test-php.yml/badge.svg)](https://github.com/AlexSkrypnyk/phpunit-helpers/actions/workflows/test-php.yml)[![codecov](https://camo.githubusercontent.com/866314956438c8103be970b87b5b029246093cfc056042530a50fb0b11ae1cad/68747470733a2f2f636f6465636f762e696f2f67682f416c6578536b7279706e796b2f706870756e69742d68656c706572732f67726170682f62616467652e7376673f746f6b656e3d37574542314958425954)](https://codecov.io/gh/AlexSkrypnyk/phpunit-helpers)[![GitHub release (latest by date)](https://camo.githubusercontent.com/86ffb817e3daf4c426ee6182b52e688c88504e2bfee0dceb05e9ca58305e9c7d/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f416c6578536b7279706e796b2f706870756e69742d68656c70657273)](https://camo.githubusercontent.com/86ffb817e3daf4c426ee6182b52e688c88504e2bfee0dceb05e9ca58305e9c7d/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f416c6578536b7279706e796b2f706870756e69742d68656c70657273)[![LICENSE](https://camo.githubusercontent.com/1c7291a2eeef029be5179c947de691f1f07b41d5c82b6baaeb8a0319d4202ae0/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f416c6578536b7279706e796b2f706870756e69742d68656c70657273)](https://camo.githubusercontent.com/1c7291a2eeef029be5179c947de691f1f07b41d5c82b6baaeb8a0319d4202ae0/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f416c6578536b7279706e796b2f706870756e69742d68656c70657273)[![Renovate](https://camo.githubusercontent.com/35389190ce58a3690fe850342c1c3fd4f54e4c10ba8996741c8558ee24bf50dc/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f72656e6f766174652d656e61626c65642d677265656e3f6c6f676f3d72656e6f76617465626f74)](https://camo.githubusercontent.com/35389190ce58a3690fe850342c1c3fd4f54e4c10ba8996741c8558ee24bf50dc/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f72656e6f766174652d656e61626c65642d677265656e3f6c6f676f3d72656e6f76617465626f74)

---

Features
--------

[](#features)

NameSourceDescription[`UnitTestCase`](#UnitTestCase)[src](src/UnitTestCase.php)Base test class that includes essential traits for PHPUnit testing[`AssertArrayTrait`](#assertarraytrait)[src](src/Traits/AssertArrayTrait.php)Custom assertions for arrays[`ApplicationTrait`](#applicationtrait)[src](src/Traits/ApplicationTrait.php)Test Symfony Console applications with assertions[`EnvTrait`](#envtrait)[src](src/Traits/EnvTrait.php)Manage environment variables during tests[`LocationsTrait`](#locationstrait)[src](src/Traits/LocationsTrait.php)Manage file system locations and directories for tests[`ProcessTrait`](#processtrait)[src](src/Traits/ProcessTrait.php)Run and assert on command line processes during tests[`ReflectionTrait`](#reflectiontrait)[src](src/Traits/ReflectionTrait.php)Access protected/private methods and properties[`SerializableClosureTrait`](#serializableclosuretrait)[src](src/Traits/SerializableClosureTrait.php)Make closures serializable for use in data providers[`StringTrait`](#stringtrait)[src](src/Traits/StringTrait.php)Advanced string assertions with exact/substring matching[`TuiTrait`](#tuitrait)[src](src/Traits/TuiTrait.php)Interact with and test Textual User Interfaces[`LoggerTrait`](#loggertrait)[src](src/Traits/LoggerTrait.php)Comprehensive hierarchical logging system for test debuggingInstallation
------------

[](#installation)

```
composer require --dev alexskrypnyk/phpunit-helpers

```

Usage
-----

[](#usage)

This package provides a collection of traits that can be used in your PHPUnit tests to make testing easier. Below is a description of each trait and how to use it.

### `UnitTestCase`

[](#unittestcase)

The `UnitTestCase` class is the base class for unit tests. It includes the `ReflectionTrait` and `LocationsTrait` to provide useful methods for testing.

The class also provides an `info()` method that collects additional information about the test from methods that end with 'Info'. Methods containing "test" in their name are automatically excluded from info collection to avoid conflicts with test methods.

```
use AlexSkrypnyk\PhpunitHelpers\UnitTestCase;

class MyTest extends UnitTestCase {
  public function testExample() {
    // Test implementation that benefits from included traits.
    echo $this->info(); // Displays information from *Info methods
  }

  public static function environmentInfo(): string {
    return 'Environment: ' . getenv('APP_ENV');
  }

  public function testFixtureInfo(): string {
    // This method will be excluded from info() output
    // because it contains "test" in the method name
    return 'This will not appear in info()';
  }
}
```

### `AssertArrayTrait`

[](#assertarraytrait)

The `AssertArrayTrait` provides custom assertions for arrays.

```
use AlexSkrypnyk\PhpunitHelpers\Traits\AssertArrayTrait;
use PHPUnit\Framework\TestCase;

class MyAssertArrayTest extends TestCase {
  use AssertArrayTrait;

  public function testCustomAssertions() {
    $array = ['This is a test', 'Another value'];

    // Assert that a string is present in an array.
    $this->assertArrayContainsString('test', $array);

    // Assert with custom failure message.
    $this->assertArrayContainsString('test', $array, 'Custom message: test string not found');
  }
}
```

### `ApplicationTrait`

[](#applicationtrait)

The `ApplicationTrait` provides methods to test Symfony Console applications and their commands with comprehensive assertions.

```
use AlexSkrypnyk\PhpunitHelpers\Traits\ApplicationTrait;
use PHPUnit\Framework\TestCase;

class MyApplicationTest extends TestCase {
  use ApplicationTrait;

  protected function setUp(): void {
    // Configure application behavior
    $this->applicationCwd = NULL; // Current working directory (NULL for current PHP process dir)
    $this->applicationShowOutput = FALSE; // Whether to show output during execution
  }

  protected function tearDown(): void {
    // Clean up application resources
    $this->applicationTearDown();
  }

  public function testConsoleApplication() {
    // Initialize application from a loader file
    $this->applicationInitFromLoader('/path/to/application_loader.php');

    // Or initialize from a command class
    $this->applicationInitFromCommand(MyCommand::class, TRUE); // TRUE for making it the default command

    // Run the application with input arguments and options
    $output = $this->applicationRun(
      ['argument1', '--option1=value1'],  // Input arguments and options
      ['capture_stderr_separately' => TRUE], // Application tester options
      FALSE // Whether a failure is expected (default: FALSE)
    );

    // Assert that the application executed successfully
    $this->assertApplicationSuccessful();

    // Or assert that the application failed
    $this->assertApplicationFailed();

    // Assert that the application output contains string(s)
    $this->assertApplicationOutputContains('Expected output');
    $this->assertApplicationOutputContains(['String1', 'String2']); // Can check multiple strings

    // Assert with custom failure message
    $this->assertApplicationOutputContains('Expected output', 'Custom message: Expected output not found');

    // Assert that the application output does not contain string(s)
    $this->assertApplicationOutputNotContains('Unexpected output');

    // Assert that the application error output contains string(s)
    $this->assertApplicationErrorOutputContains('Expected error');

    // Assert that the application error output does not contain string(s)
    $this->assertApplicationErrorOutputNotContains('Unexpected error');

    // Assert in one call using four single-character prefixes:
    // '+' = exact match present, '*' = substring present
    // '-' = exact match absent,  '!' = substring absent

    // Shortcut mode: no prefixes means all strings should be present as substrings
    $this->assertApplicationOutputContainsOrNot(['Expected', 'Output']);

    // Mixed mode: if any string has prefix, ALL must have prefixes
    $this->assertApplicationOutputContainsOrNot(['* Expected', '! Unexpected']);
    $this->assertApplicationErrorOutputContainsOrNot(['* Expected error', '! Unexpected error']);

    // Assert that combined output (standard + error) contains or does not contain string(s)
    // Shortcut mode (no prefixes)
    $this->assertApplicationAnyOutputContainsOrNot(['Expected', 'in either output']);

    // Mixed mode with prefixes
    $this->assertApplicationAnyOutputContainsOrNot([
      '* Expected in either output',
      '! Should not be in any output',
    ]);

    // Exact match examples
    $this->assertApplicationOutputContainsOrNot([
      '+ Exact output match',  // Output must equal this exactly
      '- Not exact match',      // Output must not equal this exactly
    ]);

    // Get debug info about the application (output, error output)
    echo $this->applicationInfo();
  }
}
```

### `EnvTrait`

[](#envtrait)

The `EnvTrait` helps manage environment variables during tests.

```
use AlexSkrypnyk\PhpunitHelpers\Traits\EnvTrait;
use PHPUnit\Framework\TestCase;

class MyEnvTest extends TestCase {
  use EnvTrait;

  public function testEnvironmentVariables() {
    // Set an environment variable.
    self::envSet('MY_VAR', 'value');

    // Set multiple environment variables.
    self::envSetMultiple(['VAR1' => 'value1', 'VAR2' => 'value2']);

    // Get an environment variable.
    $value = self::envGet('MY_VAR');

    // Check if an environment variable is set.
    $isSet = self::envIsSet('MY_VAR');

    // Unset an environment variable.
    self::envUnset('MY_VAR');

    // Unset multiple environment variables.
    self::envUnsetMultiple(['VAR1', 'VAR2']);

    // Unset all environment variables with a specific prefix.
    self::envUnsetPrefix('MY_');

    // Reset all environment variables.
    self::envReset();
  }
}
```

### `LocationsTrait`

[](#locationstrait)

The `LocationsTrait` provides methods to manage file system locations during tests. It maintains a set of predefined directories as static properties.

```
use AlexSkrypnyk\PhpunitHelpers\Traits\LocationsTrait;
use PHPUnit\Framework\TestCase;

class MyLocationsTest extends TestCase {
  use LocationsTrait;

  protected function setUp(): void {
    // Initialize test directories.
    self::locationsInit();

    // Now you can use the predefined directory properties:
    echo self::$root;      // Root directory of the project
    echo self::$fixtures;  // Path to fixtures directory
    echo self::$workspace; // Main workspace directory for test run
    echo self::$repo;      // Source directory for operations
    echo self::$sut;       // System Under Test directory where tests run
    echo self::$tmp;       // Temporary files directory

    // You can also print all locations with:
    echo self::locationsInfo();
  }

  protected function tearDown(): void {
    // Clean up test directories.
    self::locationsTearDown();
  }

  public function testFileOperations() {
    // Get a specific fixtures directory path.
    $fixturesDir = self::locationsFixtureDir('my-fixture');

    // Copy files to the SUT directory.
    $files = self::locationsCopyFilesToSut(['file1.txt', 'file2.txt']);

    // Files will be available in self::$sut directory
    $this->assertFileExists(self::$sut . '/file1.txt1234'); // Note: random suffix added by default
  }
}
```

### `ProcessTrait`

[](#processtrait)

The `ProcessTrait` provides methods to run command line processes and assert on their output and exit codes. It integrates with the Symfony Process component for safe and controlled command execution.

```
use AlexSkrypnyk\PhpunitHelpers\Traits\ProcessTrait;
use PHPUnit\Framework\TestCase;

class MyProcessTest extends TestCase {
  use ProcessTrait;

  protected function setUp(): void {
    // Configure process behavior.
    $this->processCwd = NULL; // Current working directory (NULL for current PHP process dir).
    $this->processStreamOutput = FALSE; // Whether to stream an output during process execution.
  }

  protected function tearDown(): void {
    // Stop any running processes.
    $this->processTearDown();
  }

  public function testCommandExecution() {
    // Run a command with arguments, inputs, environment variables, and timeouts.
    // The method validates command safety and ensures all arguments are scalar values.
    $process = $this->processRun(
      'echo',                        // Command to execute
      ['Hello', 'World'],            // Command arguments
      ['Input1', 'Input2'],          // Interactive process inputs
      ['ENV_VAR' => 'value'],        // Environment variables
      60,                            // Process timeout in seconds
      30                             // Process idle timeout in seconds
    );

    // Assert that the process executed successfully.
    $this->assertProcessSuccessful();

    // Assert that the process failed.
    $this->assertProcessFailed();

    // Assert that the process output contains string(s).
    $this->assertProcessOutputContains('Hello World');
    $this->assertProcessOutputContains(['Hello', 'World']); // Can check for multiple strings

    // Assert with custom failure message.
    $this->assertProcessOutputContains('Hello World', 'Custom message: Expected output not found');

    // Assert that the process output does not contain string(s).
    $this->assertProcessOutputNotContains('Error');
    $this->assertProcessOutputNotContains('Error', 'Custom message: Unexpected error found');
    $this->assertProcessOutputNotContains(['Error1', 'Error2']); // Can check multiple strings

    // Assert that the process error output contains string(s).
    $this->assertProcessErrorOutputContains('Warning');
    $this->assertProcessErrorOutputContains(['Warning1', 'Warning2']); // Can check multiple strings

    // Assert that the process error output does not contain string(s).
    $this->assertProcessErrorOutputNotContains('Critical');
    $this->assertProcessErrorOutputNotContains(['Critical1', 'Critical2']); // Can check multiple strings

    // Assert with advanced prefix control using four single-character prefixes:
    // '+' = exact match present, '*' = substring present
    // '-' = exact match absent,  '!' = substring absent

    // Shortcut mode: no prefixes means all strings should be present as substrings.
    $this->assertProcessOutputContainsOrNot(['Hello', 'World']); // All should be present

    // Mixed mode: if any string has prefix, ALL must have prefixes.
    $this->assertProcessOutputContainsOrNot(['+ Hello', '! Error']);
    $this->assertProcessErrorOutputContainsOrNot(['* Warning', '- Critical']);

    // Assert that combined output (stdout + stderr) contains string(s).
    $this->assertProcessAnyOutputContains('Expected in either output');
    $this->assertProcessAnyOutputContains(['String1', 'String2']); // Can check multiple strings

    // Assert that combined output (stdout + stderr) does not contain string(s).
    $this->assertProcessAnyOutputNotContains('Should not appear anywhere');
    $this->assertProcessAnyOutputNotContains(['Unwanted1', 'Unwanted2']); // Can check multiple strings

    // Assert combined output with advanced prefix control.
    $this->assertProcessAnyOutputContainsOrNot(['+ Expected', '! Unwanted']);

    // Get debug info about the process (output, error output).
    echo $this->processInfo();
  }
}
```

### `SerializableClosureTrait`

[](#serializableclosuretrait)

The `SerializableClosureTrait` makes closures serializable so they can be used in data providers. It works with both traditional closures and arrow functions.

```
use AlexSkrypnyk\PhpunitHelpers\Traits\SerializableClosureTrait;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

class MyClosureTest extends TestCase {
  use SerializableClosureTrait;

  #[DataProvider('dataProvider')]
  public function testWithClosure($callback) {
    // Unwrap the closure before using it.
    $callback = self::cu($callback);
    $result = $callback('argument');
    $this->assertEquals('ARGUMENT', $result);
  }

  public static function dataProvider() {
    return [
      'traditional' => [
        self::cw(function($value) {
          return strtoupper($value);
        })
      ],
      'arrow_function' => [
        self::cw(fn($value) => strtoupper($value))
      ],
    ];
  }
}
```

### `ReflectionTrait`

[](#reflectiontrait)

The `ReflectionTrait` provides methods to access and manipulate protected or private members of classes or objects.

```
use AlexSkrypnyk\PhpunitHelpers\Traits\ReflectionTrait;
use PHPUnit\Framework\TestCase;

class MyReflectionTest extends TestCase {
  use ReflectionTrait;

  public function testProtectedMethod() {
    $object = new SomeClass();

    // Call a protected method.
    $result = self::callProtectedMethod($object, 'protectedMethod', ['argument']);

    // Set a protected property value.
    self::setProtectedValue($object, 'protectedProperty', 'new value');

    // Get a protected property value.
    $value = self::getProtectedValue($object, 'protectedProperty');
  }
}
```

### `TuiTrait`

[](#tuitrait)

The `TuiTrait` provides constants and methods for interacting with a Textual User Interface (TUI) during tests, handling keystroke simulation and input entries. It supports both full-string input and character-by-character input simulation.

```
use AlexSkrypnyk\PhpunitHelpers\Traits\TuiTrait;
use PHPUnit\Framework\TestCase;

class MyTuiTest extends TestCase {
  use TuiTrait;

  public function testTuiInteraction() {
    // Define default entries for all sets.
    $default_entries = [
      'answer1' => 'value1',
      'answer2' => self::TUI_DEFAULT, // Use default value (empty string by default)
      'answer3' => 'value3',
      'answer4' => 'value4',
    ];

    // First entry set: use default for 'answer1'.
    $entries_set1 = ['answer1' => self::TUI_DEFAULT] + $default_entries;
    $processed_entries = self::tuiEntries($entries_set1);

    // Process entries with a custom default value instead of empty string
    $processed_entries = self::tuiEntries($entries_set1, 'custom_default');

    // Second entry set: skip 'answer2' (will not be included in the output).
    $entries_set2 = ['answer2' => self::TUI_SKIP] + $default_entries;
    $processed_entries = self::tuiEntries($entries_set2);

    // Convert entries to keystrokes for testing character-by-character input.
    // This is useful for testing TUIs that accept input one character at a time.
    $keystrokes = self::tuiKeystrokes($entries_set1);

    // Advanced keystroke conversion with options
    $keystrokes = self::tuiKeystrokes(
      $entries_set1,           // Entries to convert
      3,                       // Number of characters to clear before entering new text
      self::KEYS['TAB'],       // Custom accept key (Enter key by default)
      self::KEYS['BACKSPACE']  // Custom clear key (Backspace by default)
    );

    // Special keys are available via constants for simulating keyboard interaction.
    // Some examples of available special keys:
    $up_key = self::KEYS['UP'];
    $enter_key = self::KEYS['ENTER'];
    $tab_key = self::KEYS['TAB'];
    $esc_key = self::KEYS['ESCAPE'];
    $ctrl_c = self::KEYS['CTRL_C'];
    $backspace = self::KEYS['BACKSPACE'];

    // Arrow keys are supported in multiple formats for compatibility
    $up_arrow = self::KEYS['UP_ARROW']; // Alternative up arrow format

    // Yes/No entries are predefined for convenience.
    $yes = self::$tuiYes; // 'y' by default
    $no = self::$tuiNo;   // 'n' by default

    // Check if a value is a special key.
    $is_key = self::tuiIsKey($enter_key); // Returns true
    $is_key = self::tuiIsKey('not_a_key'); // Returns false
  }
}
```

### `StringTrait`

[](#stringtrait)

The `StringTrait` provides simple string assertion capabilities with four single-character prefixes for substring presence and absence checks, with configurable case sensitivity.

```
use AlexSkrypnyk\PhpunitHelpers\Traits\StringTrait;
use PHPUnit\Framework\TestCase;

class MyStringTest extends TestCase {
  use StringTrait;

  public function testSimpleStringMatching() {
    $haystack = 'The quick brown fox jumps over the lazy dog';

    // Four prefix types for string control:
    $this->assertStringContainsOrNot(
      $haystack,
      [
        '+ quick',     // Exact match present
        '* brown',     // Substring present
        '- slow',      // Exact match absent
        '! elephant',  // Substring absent
      ]
    );

    // Shortcut mode (no prefixes) - all treated as substring present
    $this->assertStringContainsOrNot(
      $haystack,
      ['quick', 'brown', 'fox']  // All must be present as substrings
    );

    // Case-insensitive matching (default behavior)
    $this->assertStringContainsOrNot(
      'Hello WORLD',
      ['+ hello', '* world']
    );

    // Case-sensitive matching
    $this->assertStringContainsOrNot(
      'Hello WORLD',
      ['+ Hello', '- hello'], // 'hello' should not be found (case sensitive)
      'Expected exact match for "%s" in haystack',
      'Expected substring "%s" in haystack',
      'Expected no exact match for "%s" in haystack',
      'Expected substring "%s" not in haystack',
      '+', '*', '-', '!',  // Default prefixes
      ' ',                 // Default separator
      FALSE                // Case sensitive
    );

    // Custom prefixes
    $this->assertStringContainsOrNot(
      $haystack,
      ['# quick', '~ brown', '_ slow', '? elephant'],
      'Expected exact match for "%s" in haystack',
      'Expected substring "%s" in haystack',
      'Expected no exact match for "%s" in haystack',
      'Expected substring "%s" not in haystack',
      '#', // present exact
      '~', // present contains
      '_', // absent exact
      '?'  // absent contains
    );
  }
}
```

**Features:**

- **Four Assertion Types:** Exact vs substring matching, present vs absent
- **Four Configurable Prefixes:** `+` exact present, `*` substring present, `-` exact absent, `!` substring absent
- **Prefix Separator:** Configurable separator (space by default) between prefix and value
- **Case Sensitivity Control:** Case-insensitive by default, can be turned off
- **Mode Validation:** Enforces consistent prefix usage (all or none)
- **Standard PHPUnit Assertions:** Uses `assertEquals`, `assertStringContainsString`, `assertNotEquals`, `assertStringNotContainsString`

### `LoggerTrait`

[](#loggertrait)

The `LoggerTrait` provides comprehensive hierarchical logging system for test debugging with step tracking, timing, and nested workflows. All logging is controlled by a verbose flag that defaults to `FALSE` for clean test output.

```
use AlexSkrypnyk\PhpunitHelpers\Traits\LoggerTrait;
use PHPUnit\Framework\TestCase;

class MyLoggerTest extends TestCase {
  use LoggerTrait;

  protected function setUp(): void {
    // Enable verbose logging for debugging
    static::loggerSetVerbose(TRUE);
  }

  public function testHierarchicalWorkflow() {
    // Basic logging methods
    static::log('Basic debug message');
    static::logSection('SECTION TITLE', 'Section content');
    static::logFile('/path/to/file.txt', 'Optional description');

    // Step tracking with automatic timing and hierarchy
    static::logStepStart('Optional step message');
    static::logSubstep('Processing data');
    static::logNote('Additional context information');

    // Nested steps automatically create hierarchy
    $this->nestedStepMethod();

    static::logStepFinish('Step completed successfully');

    // Generate hierarchical summary with timing
    static::logStepSummary('WORKFLOW SUMMARY');
  }

  private function nestedStepMethod(): void {
    static::logStepStart('Nested operation');
    // Work here creates deeper hierarchy level
    static::logStepFinish('Nested operation complete');
  }
}
```

**Available logging methods:**

- `log(string)` - Basic message logging
- `logSection(string, ?string, bool, int)` - Bordered sections with optional double borders and custom width
- `logFile(string, ?string)` - File content logging with borders
- `logStepStart(?string)` - Begin step tracking with automatic method name detection
- `logStepFinish(?string)` - End step tracking with elapsed time calculation
- `logSubstep(string)` - Indented substep messages
- `logNote(string)` - Indented note messages
- `logStepSummary(?string, string)` - Hierarchical step summary table with configurable indentation
- `loggerSetVerbose(bool)` - Control verbose mode
- `loggerSetOutputStream(resource|null)` - Set custom output stream (defaults to STDERR)

**Key features:**

- Hierarchical step tracking with parent-child relationships
- Automatic timing and elapsed time calculation
- Configurable indentation for nested workflows
- Method name detection via debug\_backtrace
- Memory-efficient step stack management
- Support for custom output streams and silent mode

**Example hierarchical summary output:**

```
===============================[ WORKFLOW SUMMARY ]===============================

+----------------------------------+----------+---------+
| Step                             | Status   | Elapsed |
+----------------------------------+----------+---------+
| stepDeploymentProcess            | Complete | 2m 15s  |
|   stepDatabaseMigration          | Complete | 1m 23s  |
|   stepApplicationDeployment      | Complete | 45s     |
|     stepAssetCompilation         | Complete | 32s     |
|   stepHealthChecks               | Complete | 27s     |
+----------------------------------+----------+---------+

```

### Using Multiple Traits

[](#using-multiple-traits)

You can combine multiple traits in a single test class:

```
use AlexSkrypnyk\PhpunitHelpers\Traits\AssertArrayTrait;
use AlexSkrypnyk\PhpunitHelpers\Traits\ApplicationTrait;
use AlexSkrypnyk\PhpunitHelpers\Traits\EnvTrait;
use AlexSkrypnyk\PhpunitHelpers\Traits\LoggerTrait;
use AlexSkrypnyk\PhpunitHelpers\Traits\ProcessTrait;
use AlexSkrypnyk\PhpunitHelpers\Traits\ReflectionTrait;
use AlexSkrypnyk\PhpunitHelpers\Traits\StringTrait;
use AlexSkrypnyk\PhpunitHelpers\Traits\TuiTrait;
use PHPUnit\Framework\TestCase;

class MyCombinedTest extends TestCase {
  use AssertArrayTrait;
  use ApplicationTrait;
  use EnvTrait;
  use LoggerTrait;
  use ProcessTrait; // Note: ProcessTrait automatically includes StringTrait
  use ReflectionTrait;
  use StringTrait;
  use TuiTrait;

  // Your test methods.
}
```

Or simply extend the `UnitTestCase` class which already includes some of the most useful traits:

```
use AlexSkrypnyk\PhpunitHelpers\UnitTestCase;
use AlexSkrypnyk\PhpunitHelpers\Traits\EnvTrait;

class MyTest extends UnitTestCase {
  use EnvTrait; // Add additional traits as needed.

  // Your test methods will have access to all traits.
}
```

Maintenance
-----------

[](#maintenance)

```
composer install
composer lint
composer test

```

---

*This repository was created using the [Scaffold](https://getscaffold.dev/)project template*

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance70

Regular maintenance activity

Popularity33

Limited adoption so far

Community19

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 59.8% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~14 days

Recently: every ~43 days

Total

24

Last Release

109d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/b57b0fd0b96f77f2efa1a1889af0ae607fa139bcc1256e809ee3ebbb30907364?d=identicon)[alexdrevops](/maintainers/alexdrevops)

---

Top Contributors

[![AlexSkrypnyk](https://avatars.githubusercontent.com/u/378794?v=4)](https://github.com/AlexSkrypnyk "AlexSkrypnyk (55 commits)")[![renovate[bot]](https://avatars.githubusercontent.com/in/2740?v=4)](https://github.com/renovate[bot] "renovate[bot] (37 commits)")

###  Code Quality

Static AnalysisPHPStan, Rector

Code StylePHP\_CodeSniffer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/alexskrypnyk-phpunit-helpers/health.svg)

```
[![Health](https://phpackages.com/badges/alexskrypnyk-phpunit-helpers/health.svg)](https://phpackages.com/packages/alexskrypnyk-phpunit-helpers)
```

###  Alternatives

[composer/composer

Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.

29.5k196.2M3.1k](/packages/composer-composer)[phpro/grumphp

A composer plugin that enables source code quality checks.

4.3k16.7M1.0k](/packages/phpro-grumphp)[infection/infection

Infection is a Mutation Testing framework for PHP. The mutation adequacy score can be used to measure the effectiveness of a test set in terms of its ability to detect faults.

2.2k28.9M2.4k](/packages/infection-infection)[drupal/core

Drupal is an open source content management platform powering millions of websites and applications.

21866.0M1.7k](/packages/drupal-core)[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.5k5.9M737](/packages/sylius-sylius)[drupal/core-recommended

Locked core dependencies; require this project INSTEAD OF drupal/core.

6942.5M421](/packages/drupal-core-recommended)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
