PHPackages                             ryunosuke/phpunit-extension - 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. ryunosuke/phpunit-extension

ActiveLibrary

ryunosuke/phpunit-extension
===========================

PHPUnit Fluent interface and Custom assertions

v4.3.1(1mo ago)01.2k—6.1%1MITPHPPHP &gt;=8.0

Since Jan 6Pushed 1mo ago1 watchersCompare

[ Source](https://github.com/arima-ryunosuke/phpunit-extension)[ Packagist](https://packagist.org/packages/ryunosuke/phpunit-extension)[ RSS](/packages/ryunosuke-phpunit-extension/feed)WikiDiscussions master Synced today

READMEChangelogDependencies (2)Versions (47)Used By (1)

PHPUnit Extension
=================

[](#phpunit-extension)

Description
-----------

[](#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
-------

[](#install)

```
{
    "require-dev": {
        "ryunosuke/phpunit-extension": "dev-master"
    }
}
```

Usage
-----

[](#usage)

### Actual class

[](#actual-class)

Simplified chart:

methoddescriptionreturn type\_\_callcall original method no thrownactual of method's return or expcetion\_\_call(...\[\])get original method's callableactual of method's callable (with bindings)\_\_invokecall original \_\_invoke no thrownactual of \_\_invoke's return or expcetion\_\_invoke(...\[\])get original \_\_invoke's callableactual of \_\_invoke's callable (with bindings)\_\_getget original property no thrownactual of property or expcetion\_\_setset original property publiclyvoidoffsetGetget ArrayAccess by keyactual of key's valuevarget propertyoriginal propertyuseget original method's callableoriginal method as Closurecallableget original method's callableactual of method's callablefnget original invoke's callableactual of invoke's callabledocall original methodactual of method's returntrycall original method no thrownactual of method's return or expcetionnewcall class construct no thrownactual of object or expcetioninsteadofapply callableactual of applied valuelistreturn reference argumentactual of reference argumentreturnreturn originaloriginalechodump original$thisevalassert constraint directly$thisasset assertion message$thisbreakmark breakable test$thisandreturn latest asserted actualactual of latest assertedfinalreturn assertion statisticactual of assertion statisticdeclarerewrite source by actual value$thiswasOutputedassert stdout or echo$thiswasErroredassert stderr or throw$thisinElapsedTimeassert 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

[](#custom-constraints)

Internals:

constraintdescriptionClosesToassert float by auto approximationContainsassert string/iterable/file contains substring/element/contentDatetimeEqualsassert Datetimable equalsEqualsFileassert string equals fileEqualsIgnoreWSassert string equals ignoring whitespaceEqualsPathassert path equals other path (compatible posix)FileContainsassert file contains stringFileEqualsassert file equals stringFileSizeIsassert file sizeHasKeyassert array/object has key/propertyHtmlMatchesArrayassert html string by arrayInTimeassert processing in timeIsassert value with looselyIsBetweenassert range of numberIsBlankassert blank stringIsCTypeassert value by ctype\_xxxIsFalsyassert value like a falseIsThrowableassert value is ThrowableIsTruthyassert value like a trueIsValidassert value by filter\_varJsonMatchesArrayassert json string based on arrayLengthEqualsassert string/iterable/file length/count/sizeMatchesCountEqualsassert matched count element per arrayOutputMatchesassert output of STDOUTStringLengthEqualsassert length of stringSubsetEqualsassert array by subarraySubsetMatchesassert array at preg\_matchThrowsassert callable should throw exceptionAlias:

`\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

[](#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

[](#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 =&gt; 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

[](#custom-printer)

This package provides Progress Printer. This printer outputs only in case of failure. It will not output on success.

```

```

### Custom other

[](#custom-other)

```
# e.g. bootstrap.php
ryunosuke\PHPUnit\Replacer::insteadOf();
```

#### Exporter

[](#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

[](#codecoverage)

This package provides Custom CodeCoverage. This CodeCoverage changes on the following.

- Suppports `@codeCoverageIgnore` trailing comment
    - e.g. `foo(); // @codeCoverageIgnore because php8.1 only`

Release
-------

[](#release)

Versioning is Semantic Versioning.

### 4.3.1

[](#431)

- \[merge\] Merge tag 'v3.22.1'

### 4.3.0

[](#430)

- \[feature\] stub 生成に影響があるので型を強化
- \[merge\] Merge tag 'v3.22.0'

### 4.2.2

[](#422)

- \[fixbug\] 一部の意味のある引数で all,any が出ていない
- \[merge\] Merge tag 'v3.21.0'

### 4.2.1

[](#421)

- \[fixbug\] fixed error in php8.2
- \[merge\] 3.20.2

### 4.2.0

[](#420)

- \[feature\] added tryableCallable
- \[change\] fixed stub generation

### 4.1.0

[](#410)

- \[change\] fixed ProgressPrinter

### 4.0.0

[](#400)

- \[change\] php&gt;=8.0
- \[\*change\] delete deprecated feature

### 3.22.1

[](#3221)

- \[fixbug\] 処理が速すぎると Division by zero が出る不具合

### 3.22.0

[](#3220)

- \[feature\] actual の debugInfo をそのまま生かす機能
- \[feature\] actual の iterable をそのまま生かす機能

### 3.21.0

[](#3210)

- \[feature\] エラーを例外として扱う機能

### 3.20.2

[](#3202)

- \[fixbug\] fix declare escape

### 3.20.1

[](#3201)

- \[fixbug\] fixed Start/End is not ignored

### 3.20.0

[](#3200)

- \[feature\] use compatible original class

### 3.19.0

[](#3190)

- \[feature\] added finalize
- \[feature\] improved Traversable

### 3.18.0

[](#3180)

- \[feature\] added VALID\_DOMAIN/VALID\_HOSTNAME to IsValid

### 3.17.0

[](#3170)

- \[feature\] added getClassMap/getClassByDirectory/getClassByNamespace
- \[feature\] added IsTypeOf constraint

### 3.16.0

[](#3160)

- \[feature\] added insteadof
- \[change\] obsolete clear global states

### 3.15.0

[](#3150)

- \[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

[](#3140)

- \[feature\] added TraversableComparator
- \[fixbug\] fixed self/static type
- \[fixbug\] fixed multiple markfile

### 3.13.1

[](#3131)

- \[fixbug\] fixed sub-processes did not terminate when test failed.
- \[fixbug\] fixed single backquote noticed on Windows

### 3.13.0

[](#3130)

- \[feature\] add after report
- \[feature\] generateStub supports glob pattern

### 3.12.0

[](#3120)

- \[change\] suppressed warning at warning test
- \[feature\] added backgroundTask
- \[fixbug\] fixed mixin doesn't append no generated stub

### 3.11.0

[](#3110)

- \[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

[](#3101)

- \[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

[](#3100)

- \[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

[](#390)

- \[change\] fixed printer oddities
    - improved portability
    - prefer specified columns
    - enable verbosity
    - print result on interrupt

### 3.8.1

[](#381)

- \[feature\] mark risky not asserting anything
- \[feature\] added wasOutputed/wasErrored/inElapsedTime method

### 3.8.0

[](#380)

- \[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

[](#371)

- \[fixbug\] fixed broken dependency

### 3.7.0

[](#370)

- \[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

[](#360)

- \[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

[](#350)

- \[feature\] added htmlMatchesArray supports style attribute
- \[fixbug\] fixed "try" catches necessary exceptions
- \[change\] implemented \_\_callStatic omission

### 3.4.0

[](#340)

- \[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

[](#330)

- \[feature\] ProgressPrinter to show file location on failure
- \[feature\] htmlMatchesArray made it easier to understand when A fails

### 3.2.0

[](#320)

- \[feature\] add bootstrap.php for boilerplates
- \[feature\] print Actual value

### 3.1.0

[](#310)

- \[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

[](#301)

- \[fixbug\] vendor directories have difference during development and release
- \[fixbug\] callable that not closure/object throws exception

### 3.0.0

[](#300)

- \[\*change\] see log

### 2.0.1

[](#201)

- \[feature\] support php8

### 2.0.0

[](#200)

- \[\*change\] see log

### 1.2.0

[](#120)

- \[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

[](#112)

- \[feature\] add "InTime" constraint
- \[feature\] add "callable" method
- \[change\] deprecated "catch" and "print" method

### 1.1.1

[](#111)

- \[fixbug\] get/offsetGet implementation leak
    - \_\_get: use stringToStructure
    - offsetGet: access to original offset

### 1.1.0

[](#110)

- \[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 -&gt; FileNotExists)
    - "notFileExists" can still be used, but will be deleted in the future
- \[change\] rename "all\*" -&gt; "each\*"
    - "all\*" can still be used, but will be deleted in the future
- \[fixbug\] normalize directory separator

### 1.0.0

[](#100)

- release 1.0.0
- \[change\] drastic change
- \[feature\] add "function" method
- \[feature\] add "foreach" method
- \[feature\] support "Throws" multiple arguments

### 0.2.0

[](#020)

- \[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

[](#010)

- \[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

[](#000)

- publish

License
-------

[](#license)

MIT

###  Health Score

53

—

FairBetter than 97% of packages

Maintenance94

Actively maintained with recent releases

Popularity21

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity74

Established project with proven stability

 Bus Factor1

Top contributor holds 100% 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 ~49 days

Recently: every ~71 days

Total

46

Last Release

31d ago

Major Versions

v3.20.1 → v4.0.02024-05-02

v3.20.2 → v4.2.12024-08-04

v3.21.0 → v4.2.22025-05-10

v3.22.0 → v4.3.02025-10-10

v3.22.1 → v4.3.12026-02-18

PHP version history (3 changes)v1.0.0PHP &gt;=7.2

v3.0.0PHP &gt;=7.4

v4.0.0PHP &gt;=8.0

### Community

Maintainers

![](https://www.gravatar.com/avatar/e51e177034dd7673cfaa86a43d5236ed7a6bc94f32d05c9100b039d908cd1174?d=identicon)[arima-ryunosuke](/maintainers/arima-ryunosuke)

---

Top Contributors

[![arima-ryunosuke](https://avatars.githubusercontent.com/u/7457522?v=4)](https://github.com/arima-ryunosuke "arima-ryunosuke (277 commits)")

### Embed Badge

![Health badge](/badges/ryunosuke-phpunit-extension/health.svg)

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

###  Alternatives

[codeception/lib-asserts

Assertion methods used by Codeception core and Asserts module

10456.8M14](/packages/codeception-lib-asserts)[lastzero/test-tools

Increases testing productivity by adding a service container and self-initializing fakes to PHPUnit

2244.0k13](/packages/lastzero-test-tools)

PHPackages © 2026

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