ryunosuke / phpunit-extension
PHPUnit Fluent interface and Custom assertions
Requires
- php: >=8.0
- phpunit/phpunit: 9.*
Requires (Dev)
- ryunosuke/functions: 2.*
- dev-master
- v4.2.1
- v4.2.0
- v4.1.0
- v4.0.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: 2024-11-04 11:37:41 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:
# 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:
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
@codeCoverageIgnore
trailing comment- e.g.
foo(); // @codeCoverageIgnore because php8.1 only
- e.g.
Release
Versioning is Semantic Versioning.
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.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