alexskrypnyk/phpunit-helpers

Helpers to work with PHPUnit

0.4.0 2025-04-15 08:02 UTC

README

PHPUnit Helpers logo

Helpers to work with PHPUnit

GitHub Issues GitHub Pull Requests Test PHP codecov GitHub release (latest by date) LICENSE Renovate

Features

Name Source Description
UnitTestCase src Base test class that includes essential traits for PHPUnit testing
AssertArrayTrait src Custom assertions for arrays
ApplicationTrait src Test Symfony Console applications with assertions
EnvTrait src Manage environment variables during tests
LocationsTrait src Manage file system locations and directories for tests
ProcessTrait src Run and assert on command line processes during tests
ReflectionTrait src Access protected/private methods and properties
SerializableClosureTrait src Make closures serializable for use in data providers
TuiTrait src Interact with and test Textual User Interfaces

Installation

composer require --dev alexskrypnyk/phpunit-helpers

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

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

use AlexSkrypnyk\PhpunitHelpers\UnitTestCase;

class MyTest extends UnitTestCase {
  public function testExample() {
    // Test implementation that benefits from included traits.
  }
}

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);
  }
}

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 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 - prefix with '---' for strings that should NOT be present
    $this->assertApplicationOutputContainsOrNot(['Expected', '---Unexpected']);
    $this->assertApplicationErrorOutputContainsOrNot(['Expected error', '---Unexpected error']);
    
    // Get debug info about the application (output, error output)
    echo $this->applicationInfo();
  }
}

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 all environment variables with a specific prefix.
    self::envUnsetPrefix('MY_');

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

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

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->processShowOutput = FALSE; // Whether to show 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 that the process output does not contain string(s).
    $this->assertProcessOutputNotContains('Error');
    $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 in one call - prefix with '---' for strings that should NOT be present.
    $this->assertProcessOutputContainsOrNot(['Hello', '---Error']);
    $this->assertProcessErrorOutputContainsOrNot(['Warning', '---Critical']);

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

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

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

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
  }
}

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\ProcessTrait;
use AlexSkrypnyk\PhpunitHelpers\Traits\ReflectionTrait;
use AlexSkrypnyk\PhpunitHelpers\Traits\TuiTrait;
use PHPUnit\Framework\TestCase;

class MyCombinedTest extends TestCase {
  use AssertArrayTrait;
  use ApplicationTrait;
  use EnvTrait;
  use ProcessTrait;
  use ReflectionTrait;
  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

composer install
composer lint
composer test

This repository was created using the Scaffold project template