Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
Change Log
==========

## 0.1.1 August 18, 2025
## 0.2.0 August 18, 2025

- Bug #11: Refactor project structure and update dependencies (@terabytesoftw)
- Enh #12: Add `TestSupport` trait and corresponding test suite for inaccessible properties and methods (@terabytesoftw)

## 0.1.0 January 21, 2024

Expand Down
131 changes: 85 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
- Invoke inaccessible methods to expand testing coverage.

✅ **Cross-Platform String Assertions**
- Eliminate false positives/negatives caused by Windows vs. Unix line-ending differences.
- Avoid false positives and negatives caused by Windows vs. Unix line ending differences.
- Normalize line endings for consistent string comparisons across platforms.

✅ **File System Test Management**
Expand All @@ -50,7 +50,7 @@
Install the extension.

```bash
composer require --dev --prefer-dist php-forge/support:^0.1
composer require --dev --prefer-dist php-forge/support:^0.2
```

#### Method 2: Manual installation
Expand All @@ -60,7 +60,7 @@ Add to your `composer.json`.
```json
{
"require-dev": {
"php-forge/support": "^0.1"
"php-forge/support": "^0.2"
}
}
```
Expand All @@ -77,94 +77,133 @@ composer update

```php
<?php

declare(strict_types=1);

use PHPForge\Support\Assert;
use PHPForge\Support\TestSupport;
use PHPUnit\Framework\TestCase;

$object = new class () {
private string $secretValue = 'hidden';
};
final class AccessPrivatePropertyTest extends TestCase
{
use TestSupport;

// access private properties for testing
$value = Assert::inaccessibleProperty($object, 'secretValue');
public function testInaccessibleProperty(): void
{
$object = new class () {
private string $secretValue = 'hidden';
};

self::assertSame('hidden', $value);
$value = self::inaccessibleProperty($object, 'secretValue');

self::assertSame('hidden', $value, "Should access the private property and return its value.");
}
}
```

### Equals without line ending
### Invoking protected methods

```php
<?php

declare(strict_types=1);

use PHPForge\Support\Assert;
use PHPForge\Support\TestSupport;
use PHPUnit\Framework\TestCase;

final class InvokeProtectedMethodTest extends TestCase
{
use TestSupport;

public function testInvokeMethod(): void
{
$object = new class () {
protected function calculate(int $a, int $b): int
{
return $a + $b;
}
};

// normalize line endings for consistent comparisons
Assert::equalsWithoutLE(
"Foo\r\nBar",
"Foo\nBar",
"Should match regardless of line ending style"
);
$result = self::invokeMethod($object, 'calculate', [5, 3]);

self::assertSame(8, $result, "Should invoke the protected method and return the correct sum.");
}
}
```

### Invoking protected methods
### Normalize line endings

```php
<?php

declare(strict_types=1);

use PHPForge\Support\Assert;
use PHPForge\Support\TestSupport;
use PHPUnit\Framework\TestCase;

$object = new class () {
protected function calculate(int $a, int $b): int
final class NormalizeLineEndingsTest extends TestCase
{
use TestSupport;

public function testNormalizedComparison(): void
{
return $a + $b;
self::assertSame(
self::normalizeLineEndings("Foo\r\nBar"),
self::normalizeLineEndings("Foo\nBar"),
"Should match regardless of line ending style",
);
}
};

// test protected method behavior
$result = Assert::invokeMethod($object, 'calculate', [5, 3]);

self::assertSame(8, $result);
}
```

### Remove files from directory

```php
<?php

declare(strict_types=1);

use PHPForge\Support\Assert;
use PHPForge\Support\TestSupport;
use PHPUnit\Framework\TestCase;

final class RemoveFilesFromDirectoryTest extends TestCase
{
use TestSupport;

public function testCleanup(): void
{
$testDir = dirname(__DIR__) . '/runtime';
// clean up test artifacts (preserves '.gitignore' and '.gitkeep')

$testDir = dirname(__DIR__) . '/runtime';
self::removeFilesFromDirectory($testDir);

// clean up test artifacts (preserves '.gitignore' and '.gitkeep')
Assert::removeFilesFromDirectory($testDir);
self::assertTrue(true, "Should remove all files in the test directory while preserving Git-tracked files.");
}
}
```

### Set inaccessible property

```php
<?php

declare(strict_types=1);

use PHPForge\Support\Assert;
use PHPForge\Support\TestSupport;
use PHPUnit\Framework\TestCase;

final class SetInaccessiblePropertyTest extends TestCase
{
use TestSupport;

$object = new class () {
private string $config = 'default';
};
public function testSetProperty(): void
{
$object = new class () {
private string $config = 'default';
};

// set private property for testing scenarios
Assert::setInaccessibleProperty($object, 'config', 'test-mode');
// set private property for testing scenarios
self::setInaccessibleProperty($object, 'config', 'test-mode');

$newValue = Assert::inaccessibleProperty($object, 'config');
$newValue = self::inaccessibleProperty($object, 'config');

self::assertSame('test-mode', $newValue);
self::assertSame('test-mode', $newValue, "Should set the inaccessible property to 'test-mode'.");
}
}
```

## Documentation
Expand Down
14 changes: 7 additions & 7 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "php-forge/support",
"type": "library",
"description": "Support library tests for PHP",
"description": "Support utilities for enhanced testing capabilities.",
"keywords": [
"php",
"php-forge",
Expand All @@ -11,16 +11,16 @@
],
"license": "BSD-3-Clause",
"require": {
"php": "^8.1",
"phpunit/phpunit": "^10.5"
"php": "^8.1"
},
"require-dev": {
"infection/infection": "^0.27|^0.31",
"maglnet/composer-require-checker": "^4.7",
"phpstan/phpstan-strict-rules": "^2.0.3",
"symplify/easy-coding-standard": "^12.5",
"phpstan/phpstan": "^2.1",
"rector/rector": "^2.1"
"phpstan/phpstan-strict-rules": "^2.0.3",
"phpunit/phpunit": "^10.5",
"rector/rector": "^2.1",
"symplify/easy-coding-standard": "^12.5"
},
"autoload": {
"psr-4": {
Expand All @@ -34,7 +34,7 @@
},
"extra": {
"branch-alias": {
"dev-main": "0.1-dev"
"dev-main": "0.3.x-dev"
}
},
"config": {
Expand Down
59 changes: 25 additions & 34 deletions src/Assert.php → src/TestSupport.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,46 +20,20 @@
use function unlink;

/**
* Assertion utility class for advanced test introspection and manipulation.
* Trait providing utilities for testing inaccessible properties, methods, and filesystem cleanup.
*
* Provides static helper methods for accessing and modifying inaccessible properties and methods invoking parent class
* logic, and performing file system cleanup in test environments.
* Supplies static helper methods for normalizing line endings, accessing or modifying private/protected properties and
* methods (including those inherited from parent classes), invoking inaccessible methods, and recursively removing
* files from directories.
*
* Extends {@see \PHPUnit\Framework\Assert} to offer additional capabilities for testing private/protected members and
* for managing test artifacts, supporting robust and isolated unit tests.
* These utilities are designed to facilitate comprehensive unit testing by enabling assertions and manipulations that
* would otherwise be restricted by visibility constraints or platform differences.
*
* Key features.
* - Access and modify inaccessible (private/protected) properties and methods via reflection.
* - Invoke parent class methods and properties for testing inheritance scenarios.
* - Normalize line endings for cross-platform string assertions.
* - Remove files and directories recursively for test environment cleanup.
*
* @copyright Copyright (C) 2025 PHPForge.
* @copyright Copyright (C) 2025 Terabytesoftw.
* @license https://opensource.org/license/bsd-3-clause BSD 3-Clause License.
*/
final class Assert extends \PHPUnit\Framework\Assert
trait TestSupport
{
/**
* Asserts that two strings are equal after normalizing line endings to unix style ('\n').
*
* Replaces all windows style ('\r\n') line endings with unix style ('\n') in both the expected and actual strings
* before performing the equality assertion.
*
* This ensures cross-platform consistency in string comparisons where line ending differences may otherwise cause
* `false` negatives.
*
* @param string $expected Expected string value, with any line endings.
* @param string $actual Actual string value, with any line endings.
* @param string $message Optional failure message to display if the assertion fails. Default is an empty string.
*/
public static function equalsWithoutLE(string $expected, string $actual, string $message = ''): void
{
$expected = str_replace("\r\n", "\n", $expected);
$actual = str_replace("\r\n", "\n", $actual);

self::assertEquals($expected, $actual, $message);
}

/**
* Retrieves the value of an inaccessible property from a parent class instance.
*
Expand Down Expand Up @@ -185,6 +159,23 @@ public static function invokeParentMethod(
return $result ?? null;
}

/**
* Normalizes line endings to Unix style ('\n') for cross-platform string assertions.
*
* Converts Windows style ('\r\n') line endings to Unix style ('\n') to ensure consistent string comparisons across
* different operating systems during testing.
*
* This method is useful for eliminating false negatives in assertions caused by platform-specific line endings.
*
* @param string $line Input string potentially containing Windows style line endings.
*
* @return string String with normalized Unix style line endings.
*/
public static function normalizeLineEndings(string $line): string
{
return str_replace(["\r\n", "\r"], "\n", $line);
}

/**
* Removes all files and directories recursively from the specified base path, excluding '.gitignore' and
* '.gitkeep'.
Expand Down
Loading
Loading