ryunosuke / phpunit-extension
PHPUnit Fluent interface and Custom assertions
Installs: 484
Dependents: 10
Suggesters: 0
Security: 0
Stars: 0
Watchers: 1
Forks: 0
Open Issues: 0
pkg:composer/ryunosuke/phpunit-extension
Requires
- php: >=8.0
 - phpunit/phpunit: 9.*
 
Requires (Dev)
- ryunosuke/functions: 2.*
 
- dev-master
 - v4.3.0
 - v4.2.2
 - v4.2.1
 - v4.2.0
 - v4.1.0
 - v4.0.0
 - v3.22.0
 - v3.21.0
 - v3.20.2
 - v3.20.1
 - v3.20.0
 - v3.19.0
 - v3.18.0
 - v3.17.0
 - v3.16.0
 - v3.15.0
 - v3.14.0
 - v3.13.1
 - v3.13.0
 - v3.12.0
 - v3.11.0
 - v3.10.1
 - v3.10.0
 - v3.9.0
 - v3.8.1
 - v3.8.0
 - v3.7.1
 - v3.7.0
 - v3.6.7
 - v3.5.0
 - v3.4.0
 - v3.3.0
 - v3.2.0
 - v3.1.0
 - v3.0.1
 - v3.0.0
 - v2.0.2
 - v2.0.1
 - v2.0.0
 - v1.2.0
 - v1.1.2
 - v1.1.1
 - v1.1.0
 - v1.0.0
 
This package is auto-updated.
Last update: 2025-10-10 06:47:14 UTC
README
Description
This package adds Fluent interface. and provides Custom assertion.
- e.g. 
that('xxx')->isEqual('xxx') - e.g. 
that(1)->isInt()->isBetween(1, 9) - e.g. 
that('qwe asd zxc')->stringStartsWith('qwe')->stringEndsWith('zxc') 
Install
{
    "require-dev": {
        "ryunosuke/phpunit-extension": "dev-master"
    }
}
Usage
Actual class
Simplified chart:
| method | description | return type | 
|---|---|---|
| __call | call original method no thrown | actual of method's return or expcetion | 
| __call(...[]) | get original method's callable | actual of method's callable (with bindings) | 
| __invoke | call original __invoke no thrown | actual of __invoke's return or expcetion | 
| __invoke(...[]) | get original __invoke's callable | actual of __invoke's callable (with bindings) | 
| __get | get original property no thrown | actual of property or expcetion | 
| __set | set original property publicly | void | 
| offsetGet | get ArrayAccess by key | actual of key's value | 
| var | get property | original property | 
| use | get original method's callable | original method as Closure | 
| callable | get original method's callable | actual of method's callable | 
| fn | get original invoke's callable | actual of invoke's callable | 
| do | call original method | actual of method's return | 
| try | call original method no thrown | actual of method's return or expcetion | 
| new | call class construct no thrown | actual of object or expcetion | 
| insteadof | apply callable | actual of applied value | 
| list | return reference argument | actual of reference argument | 
| return | return original | original | 
| echo | dump original | $this | 
| eval | assert constraint directly | $this | 
| as | set assertion message | $this | 
| break | mark breakable test | $this | 
| and | return latest asserted actual | actual of latest asserted | 
| final | return assertion statistic | actual of assertion statistic | 
| declare | rewrite source by actual value | $this | 
| wasOutputed | assert stdout or echo | $this | 
| wasErrored | assert stderr or throw | $this | 
| inElapsedTime | assert elapsed time | $this | 
# e.g. bootstrap.php /** * @template T * @param T $actual * @return \ryunosuke\PHPUnit\Actual|T */ function that($actual) { return new \ryunosuke\PHPUnit\Actual($actual); } // example TestCase class ActualTest extends \PHPUnit\Framework\TestCase { function test_fluent() { # fluent interface // means: assertThat(5, logicalAnd(isType('int'), greaterThanOrEqual(1), lessThanOrEqual(9))); that(5)->isInt()->isBetween(1, 9); } function test_prefixEach() { # "each*" asserts per values (assert AND all values) // means: assertThat(1, greaterThan(0)); assertThat(2, greaterThan(0)); assertThat(3, greaterThan(0)); that([1, 2, 3])->eachGreaterThan(0); } function test_suffixAnyAll() { # "*Any" asserts multiple arguments (assert OR all arguments) // means: assertThat('hello world', logicalOr(stringContains('hello'), stringContains('noexists'))); that('hello world')->stringContainsAny(['hello', 'noexists']); // ignore case (other arguments are normal) that('hello world')->stringContainsAny(['HELLO', 'noexists'], true); # "*All" asserts multiple arguments (assert AND all arguments) // means: assertThat('hello world', logicalAnd(stringContains('hello'), stringContains('world'))); that('hello world')->stringContainsAll(['hello', 'world']); } function test_var_use() { # "var" returns property of original object (non-public access is possible) $object = new \ArrayObject(['x' => 'X', 'y' => 'Y'], \ArrayObject::ARRAY_AS_PROPS); $property = that($object)->var('x'); assertThat($property, equalTo('X')); # "use" returns method's closure of original object (non-public access is possible) $object = new \ArrayObject(['x' => 'X', 'y' => 'Y'], \ArrayObject::ARRAY_AS_PROPS); $method = that($object)->use('getArrayCopy'); assertThat($method(), equalTo(['x' => 'X', 'y' => 'Y'])); } function test_arrayAccess() { # array access returns array's value and actual $array = ['x' => ['y' => ['z' => [1, 2, 3]]]]; // means: assertThat($array['x']['y']['z'], equalTo([1, 2, 3])); that($array)['x']['y']['z']->isEqual([1, 2, 3]); } function test_propertyAccess() { # property access returns property and actual (non-public access is possible) $object = (object) ['x' => 'X']; // means: assertThat($object->x, equalTo('X')); that($object)->x->isEqual('X'); } function test_methodCall() { # method call returns original result and actual (non-public access is possible) $object = new \ArrayObject([1, 2, 3]); // means: assertThat($object->getArrayCopy(), equalTo([1, 2, 3])); that($object)->getArrayCopy()->isEqual([1, 2, 3]); # actual's method prefers to original method $object = new \ArrayObject([1, 2, 3]); // means: assertThat($object, countOf(3)); not: $object->count(); that($object)->count(3); # "callable" returns original method's callable and actual that($object)->callable('count')->isCallable(); // "callable"'s arguments mean method arguments that($object)->callable('setIteratorClass', \stdClass::class)->throws('derived from ArrayIterator'); # "do" invokes original method and actual that($object)->do('count')->isEqual(3); # "__invoke" returns original::__invoke and actual $object = function ($a, $b) { return $a + $b; }; // means: assertThat($object(1, 2), equalTo(3)); that($object)(1, 2)->isEqual(3); } function test_methodCallWithBinding() { # method call by (...[]) returns method's callable of original object with binding (non-public access is possible) $closure = function ($arg) { echo $arg; }; that($closure)->callable('__invoke', 'hoge')->outputEquals('hoge'); that($closure)(...['hoge'])->outputEquals('hoge'); } function test_try() { # "try" is not thrown method call and actual $object = new \ReflectionObject((object) ['x' => 'X']); // returns original result and actual if not thrown that($object)->try('getProperty', 'x')->isInstanceOf(\ReflectionProperty::class); // returns thrown exception and actual if thrown that($object)->try('getProperty', 'y')->isInstanceOf(\ReflectionException::class); } function test_list() { # "list" returns reference argument and actual // means: (fn (&$ref) => $ref = 123)($dummy); assertThat($dummy, equalTo(123)); $dummy = null; that(fn (&$ref) => $ref = 123)($dummy)->list(0)->isEqual(123); } function test_return() { # "return" returns original value $object = new \stdClass(); assertSame($object, that($object)->return()); } function test_eval() { # "eval" asserts directly constraint (variadic arguments OR all arguments) // means: assertThat('x', equalTo('x')); that('x')->eval(equalTo('x')); // means: assertThat('x', logicalOr(equalTo('x'), equalTo('y'), equalTo('z'))); that('x')->eval(equalTo('x'), equalTo('y'), equalTo('z')); } function test_as() { # "as" describes failure text // means: assertThat('x', equalTo('notX'), 'this is failure message'); that('x')->as('this is failure message')->isEqual('notX'); } function test_break() { # "break" mark breakable test (converting Failure to Warning) that('x')->break()->isEqual('notX'); // ...continued this case } function test_and_exit() { # "and" returns latest actual $object = new \ArrayObject(['x' => 'abcX', 'y' => 'abcY'], \ArrayObject::ARRAY_AS_PROPS); // "and" can call as property also as below that($object) ->x->stringStartsWith('abc')->and->stringLengthEquals(4)->exit() ->y->stringStartsWith('abc')->and->stringLengthEquals(4)->exit() ->getArrayCopy()->count(2)->and->hasKey('x'); # but no need to use them as below $that = that($object); $that->getArrayCopy()->count(2)->hasKey('x')->hasKey('y'); $that->x->stringStartsWith('abc')->stringLengthEquals(4); $that->y->stringStartsWith('abc')->stringLengthEquals(4); } function test_declare() { # declare is replaced below at runtime // that(['x', 'y', 'z'])->declare(); that(['x', 'y', 'z'])->is(['x', 'y', 'z']); } }
A return value or argument of Actual can transparently use the original method, as shown below.
class Example { private int $privateField = 0; public function getPrivate() { return $this->privateField; } public function setPrivate(int $field) { $this->privateField = $field; } } class ExampleTest extends \PHPUnit\Framework\TestCase { function test() { // test object $example = that(new Example()); // directry private access $example->privateField = 3; $example->privateField->is(3); // $field is actual $field = $example->getPrivate(); $field->is(3); // but, $field can use to arguments $example->setPrivate($field); } }
Custom constraints
Internals:
| constraint | description | 
|---|---|
| ClosesTo | assert float by auto approximation | 
| Contains | assert string/iterable/file contains substring/element/content | 
| DatetimeEquals | assert Datetimable equals | 
| EqualsFile | assert string equals file | 
| EqualsIgnoreWS | assert string equals ignoring whitespace | 
| EqualsPath | assert path equals other path (compatible posix) | 
| FileContains | assert file contains string | 
| FileEquals | assert file equals string | 
| FileSizeIs | assert file size | 
| HasKey | assert array/object has key/property | 
| HtmlMatchesArray | assert html string by array | 
| InTime | assert processing in time | 
| Is | assert value with loosely | 
| IsBetween | assert range of number | 
| IsBlank | assert blank string | 
| IsCType | assert value by ctype_xxx | 
| IsFalsy | assert value like a false | 
| IsThrowable | assert value is Throwable | 
| IsTruthy | assert value like a true | 
| IsValid | assert value by filter_var | 
| JsonMatchesArray | assert json string based on array | 
| LengthEquals | assert string/iterable/file length/count/size | 
| MatchesCountEquals | assert matched count element per array | 
| OutputMatches | assert output of STDOUT | 
| StringLengthEquals | assert length of string | 
| SubsetEquals | assert array by subarray | 
| SubsetMatches | assert array at preg_match | 
| Throws | assert callable should throw exception | 
Alias:
\ryunosuke\PHPUnit\Actual::$constraintVariations is searching for variation from other constraint.
// Disable. Built-in constraints are not called \ryunosuke\PHPUnit\Actual::$constraintVariations['isSame'] = false; // Alias. This ables to use: $actual->isSame('other') \ryunosuke\PHPUnit\Actual::$constraintVariations['isSame'] = IsIdentical::class; // Construct. This ables to use: $actual->isArray() \ryunosuke\PHPUnit\Actual::$constraintVariations['isArray'] = [IsType::class => [IsType::TYPE_ARRAY]]; // Mix. This ables to use: $actual->isNullOrString() \ryunosuke\PHPUnit\Actual::$constraintVariations['isNullOrString'] = [IsNull::class, IsType::class => [IsType::TYPE_STRING]]; // Instance. This ables to use: $actual->lineCount(5) \ryunosuke\PHPUnit\Actual::$constraintVariations['lineCount'] = new class(/* argument is used as default */0) extends \PHPUnit\Framework\Constraint\Constraint { private $lineCount; public function __construct(int $lineCount) { $this->lineCount = $lineCount; } protected function matches($other): bool { return $this->lineCount === (preg_match_all("#\\R#", $other) + 1); } public function toString(): string { return 'is ' . $this->lineCount . ' lines'; } }; // Shorthand instance by closure. This is the same as above \ryunosuke\PHPUnit\Actual::$constraintVariations['lineCount2'] = function ($other, int $lineCount, string $delimiter = "\\R") { return $lineCount === (preg_match_all("#$delimiter#", $other) + 1); };
User defined:
\ryunosuke\PHPUnit\Actual::$constraintNamespaces is searching for constraint namespace.
// This ables to use: $actual->yourConstraint() \ryunosuke\PHPUnit\Actual::$constraintNamespaces['your\\namespace'] = 'your/constraint/directory';
// Disable. chain case function call \ryunosuke\PHPUnit\Actual::$functionNamespaces = [];
Code completion
Actual class is using \ryunosuke\PHPUnit\Annotation trait.
If declare this class in your project space, then custom method and code completion are enabled.
// e.g. bootstrap.php namespace ryunosuke\PHPUnit { /** * @method \ryunosuke\PHPUnit\Actual isHoge() */ trait Annotation { function isFuga(): \ryunosuke\PHPUnit\Actual { { return $this->eval(new \PHPUnit\Framework\Constraint\IsEqual('fuga')); } } }
That ables to use $actual->isH(oge) completion and $actual->isF(uga) method.
Or call \ryunosuke\PHPUnit\Actual::generateAnnotation.
This method returns annotation via $constraintVariations and $constraintNamespaces.
TestCaseTrait
This Trait provides testing utility.
- trapThrowable
- If specified exception is thrown then skip the test.
 
 - restorer
- Reset function base's value. When unset return value recovery prev value.
 
 - finalize
- Run closure at Test end.
 
 - rewriteProperty
- Rewrite private/protected property. When unset return value recovery prev value.
 
 - tryableCallable
- Closurize private/protected method. And bind arguments with default values.
 
 - getEnvOrSkip
- Return getenv(). If novalue then skip the test.
 
 - getConstOrSkip
- Return constant(). If undefined then skip the test.
 
 - getClassMap
- Return all class => file array based on composer
 
 - getClassByDirectory
- Return class names by directory
 
 - getClassByNamespace
- Return class names by namespace
 
 - emptyDirectory
- Ready temporary directory and clean contents.
 
 - backgroundTask
- Run closure asynchronously.
 
 - report
- Report message to test result footer.
 
 
Custom printer
This package provides Progress Printer. This printer outputs only in case of failure. It will not output on success.
<phpunit printerClass="\ryunosuke\PHPUnit\Printer\ProgressPrinter"> </phpunit>
Custom other
# e.g. bootstrap.php ryunosuke\PHPUnit\Replacer::insteadOf();
Exporter
This package provides Custom Exporter. This Exporter changes on the following.
- Extended maximum character width for strings
 - Changed binary string to quoted string
 - Changed to not insert tagged newline characters
 - Changed object identifier from hash to id
 
CodeCoverage
This package provides Custom CodeCoverage. This CodeCoverage changes on the following.
- Suppports 
@codeCoverageIgnoretrailing comment- e.g. 
foo(); // @codeCoverageIgnore because php8.1 only 
 - e.g. 
 
Release
Versioning is Semantic Versioning.
4.3.0
- [feature] stub 生成に影響があるので型を強化
 - [merge] Merge tag 'v3.22.0'
 
4.2.2
- [fixbug] 一部の意味のある引数で all,any が出ていない
 - [merge] Merge tag 'v3.21.0'
 
4.2.1
- [fixbug] fixed error in php8.2
 - [merge] 3.20.2
 
4.2.0
- [feature] added tryableCallable
 - [change] fixed stub generation
 
4.1.0
- [change] fixed ProgressPrinter
 
4.0.0
- [change] php>=8.0
 - [*change] delete deprecated feature
 
3.22.0
- [feature] actual の debugInfo をそのまま生かす機能
 - [feature] actual の iterable をそのまま生かす機能
 
3.21.0
- [feature] エラーを例外として扱う機能
 
3.20.2
- [fixbug] fix declare escape
 
3.20.1
- [fixbug] fixed Start/End is not ignored
 
3.20.0
- [feature] use compatible original class
 
3.19.0
- [feature] added finalize
 - [feature] improved Traversable
 
3.18.0
- [feature] added VALID_DOMAIN/VALID_HOSTNAME to IsValid
 
3.17.0
- [feature] added getClassMap/getClassByDirectory/getClassByNamespace
 - [feature] added IsTypeOf constraint
 
3.16.0
- [feature] added insteadof
 - [change] obsolete clear global states
 
3.15.0
- [refactor] code format and fix inspection
 - [feature] added clear state to that
 - [fixbug] fixed Constraints and method calls is mixed
 - [fixbug] changed getXXXOrSkip to static
 
3.14.0
- [feature] added TraversableComparator
 - [fixbug] fixed self/static type
 - [fixbug] fixed multiple markfile
 
3.13.1
- [fixbug] fixed sub-processes did not terminate when test failed.
 - [fixbug] fixed single backquote noticed on Windows
 
3.13.0
- [feature] add after report
 - [feature] generateStub supports glob pattern
 
3.12.0
- [change] suppressed warning at warning test
 - [feature] added backgroundTask
 - [fixbug] fixed mixin doesn't append no generated stub
 
3.11.0
- [change] changed ProgressPrinter format and support breakable test
 - [feature] added trapThrowable
 - [feature] added breakable
 - [change] deprecated function caller
 - [refactor] fixed wrong namespace
 
3.10.1
- [change] changed stub class is hierarchized
 - [fixbug] fixed __set does not set ancestor private field
 - [fixbug] fixed generateStub losts original type
 - [fixbug] fixed generateStub ignores public member
 
3.10.0
- [feature] implove generateStub
 - [feature] added MatchesCountEquals constaint
 - [feature] added unwrapping original value if Actual argument
 - [feature] added disable function option
 - [change] deprecated static calls with __toString of object
 - [fixbug] fixed caused exceptions to be implicitly through
 - [fixbug] fixed filesystem function denies null string
 - [fixbug] fixed __set private field
 - [fixbug] fixed "debug" method returns null always
 
3.9.0
- [change] fixed printer oddities
- improved portability
 - prefer specified columns
 - enable verbosity
 - print result on interrupt
 
 
3.8.1
- [feature] mark risky not asserting anything
 - [feature] added wasOutputed/wasErrored/inElapsedTime method
 
3.8.0
- [feature] added restorer
 - [feature] added get(Env|Const)OrSkip
 - [change] fixed ExpectationFailedException message is too large
 - [fixbug] fixed output is swallowed up
 
3.7.1
- [fixbug] fixed broken dependency
 
3.7.0
- [fixbug] fixed duplicated annotation
 - [feature] added Is constaint (looser than IsEqual)
 - [feature] added ClosesTo constaint
 - [feature] added DatetimeEquals constaint
 - [feature] supported SplFileInfo at file system
 - [change] changed as method to variable arguments
 
3.6.0
- [refactor] changed private field name to be incompatible with stub generation
 - [feature] implemented to disable built-in constraints
 - [feature] added TestCaseTrait trait
 - [feature] added declare method
 - [feature] added new method
 - [feature] added isUndefined variation
 - [feature] added EqualsPath constaint
 - [fixbug] fixed no $ in stub generation
 - [fixbug] fixed strictly enforced due to frequent unintended function calls
 - [fixbug] fixed in __callStatic where original method was not called
 
3.5.0
- [feature] added htmlMatchesArray supports style attribute
 - [fixbug] fixed "try" catches necessary exceptions
 - [change] implemented __callStatic omission
 
3.4.0
- [refactor] fixed annotation
 - [feature] added ...[] syntax
 - [feature] added stdout to results property
 - [feature] added htmlMatchesArray supports class and closure
 - [feature] added OutputMatches variation
 - [fixbug] fixed the file location was on the test code when an error on the test target
 - [fixbug] fixed progress disorder
 
3.3.0
- [feature] ProgressPrinter to show file location on failure
 - [feature] htmlMatchesArray made it easier to understand when A fails
 
3.2.0
- [feature] add bootstrap.php for boilerplates
 - [feature] print Actual value
 
3.1.0
- [feature] add final method for assertion statistic
 - [feature] add raw flag to OutputMatches constraint
 - [feature] add fn method for no-method callable
 - [refactor] Establish self describing class
 
3.0.1
- [fixbug] vendor directories have difference during development and release
 - [fixbug] callable that not closure/object throws exception
 
3.0.0
- [*change] see log
 
2.0.1
- [feature] support php8
 
2.0.0
- [*change] see log
 
1.2.0
- [feature] add Annotester class
 - [feature] add shorthand closure alias
 - [feature] add int, float ValidType
 - [feature] add constraint alias mangle argument
 - [feature] add "and" property/method
 - [fixbug] supports static property/method
 - [fixbug] supports minor/patch version of $compatibleVersion
 
1.1.2
- [feature] add "InTime" constraint
 - [feature] add "callable" method
 - [change] deprecated "catch" and "print" method
 
1.1.1
- [fixbug] get/offsetGet implementation leak
- __get: use stringToStructure
 - offsetGet: access to original offset
 
 
1.1.0
- [feature] add version control property
 - [feature] add "prefixIs", "suffixIs" alias
 - [feature] support Regex and JSONPath and JMESPath at get/offsetGet
 - [feature] implement "__toString" method
 - [feature] add depended on other constraint
 - [feature] add "FileSizeIs" constraint
 - [change] change "Not" position (e.g. NotFileExists -> FileNotExists)
- "notFileExists" can still be used, but will be deleted in the future
 
 - [change] rename "all*" -> "each*"
- "all*" can still be used, but will be deleted in the future
 
 - [fixbug] normalize directory separator
 
1.0.0
- release 1.0.0
 - [change] drastic change
 - [feature] add "function" method
 - [feature] add "foreach" method
 - [feature] support "Throws" multiple arguments
 
0.2.0
- [feature] add "var" method
 - [feature] add "use" method
 - [feature] add "print" method
 - [feature] add "return" method
 - [feature] add "OutputMatches" constraint
 - [change] delete "autoback" method
 - [change] rename class/method
 
0.1.0
- [feature] add "*All" method
 - [feature] add "try" method
 - [feature] add "message" method
 - [feature] add "__invoke" method
 - [feature] add "file*" constraint
 - [feature] replace with original "logical*" constraint
 - [feature] variation adds "is" alias
 - [feature] variation supports anonymouse class
 - [fixbug] variation ignores arguments
 - [change] __get/__call can access no-public member
 
0.0.0
- publish
 
License
MIT