PHPackages                             zenstruck/assert - 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. [Testing &amp; Quality](/categories/testing)
4. /
5. zenstruck/assert

ActiveLibrary[Testing &amp; Quality](/categories/testing)

zenstruck/assert
================

Standalone, lightweight, framework agnostic, test assertion library.

v1.7.0(5mo ago)8214.9M—0.3%5[2 PRs](https://github.com/zenstruck/assert/pulls)8MITPHPPHP &gt;=8.1CI passing

Since Aug 5Pushed 1mo ago4 watchersCompare

[ Source](https://github.com/zenstruck/assert)[ Packagist](https://packagist.org/packages/zenstruck/assert)[ Docs](https://github.com/zenstruck/assert)[ GitHub Sponsors](https://github.com/kbond)[ GitHub Sponsors](https://github.com/nikophil)[ RSS](/packages/zenstruck-assert/feed)WikiDiscussions 1.x Synced 1mo ago

READMEChangelog (10)Dependencies (5)Versions (15)Used By (8)

zenstruck/assert
================

[](#zenstruckassert)

[![CI Status](https://github.com/zenstruck/assert/workflows/CI/badge.svg)](https://github.com/zenstruck/assert/actions?query=workflow%3ACI)[![Code Coverage](https://camo.githubusercontent.com/3d988fdc43bcd5aeb430c23c498f2d497473d883127691d43c2f13469ea663c5/68747470733a2f2f636f6465636f762e696f2f67682f7a656e73747275636b2f6173736572742f6272616e63682f312e782f67726170682f62616467652e7376673f746f6b656e3d52374f48595947504b4d)](https://codecov.io/gh/zenstruck/assert)

This library allows dependency-free test assertions. When using a PHPUnit-based test library (PHPUnit itself, Pest, Codeception), failed assertions are automatically converted to PHPUnit failures and successful assertions are added to PHPUnit's successful assertion count.

This library differs from other popular assertion libraries ([webmozart/assert](https://github.com/webmozarts/assert) &amp; [beberlei/assert](https://github.com/beberlei/assert)) in that it is purely for *test assertions* opposed to what these libraries provide: *type safety assertions*.

With the exception of the [Expectation API](#expectation-api) (specifically, the [Throws Expectation](#throws-expectation) which provides a nice API for making exception assertions), this library is really only useful for 3rd party libraries that would like to provide test assertions but not have a direct dependency on a specific test library.

Installation
------------

[](#installation)

```
$ composer require zenstruck/assert
```

`Zenstruck\Assert`
------------------

[](#zenstruckassert-1)

This is the main entry point for making assertions. When the methods on this class are called, they throw a `Zenstruck\Assert\AssertionFailed` on failure. If they do not throw this exception, they are considered successful.

When using a PHPUnit-based framework, failed assertions are auto-converted to PHPUnit test failures and successful assertions are added to PHPUnit's successful assertion count.

True/False Assertions
---------------------

[](#truefalse-assertions)

```
use Zenstruck\Assert;

// passes
Assert::true(true === true, 'The condition was not true.');

// fails
Assert::true(true === false, 'The condition was not true.');

// passes
Assert::false(true === false, 'The condition was not false.');

// fails
Assert::false(true === true, 'The condition was not false.');
```

Generic Fail/Pass
-----------------

[](#generic-failpass)

```
use Zenstruck\Assert;

// trigger a "fail"
Assert::fail('This is a failure.');

// trigger a "pass"
Assert::pass();
```

Try
---

[](#try)

Attempt to run a callback and return the result. If an exception is thrown while running, a *fail* is triggered. If run successfully, a *pass* is triggered.

```
use Zenstruck\Assert;

$ret = Assert::try(fn() => 'value'); // $ret === 'value'

Assert::try(fn() => throw new \RuntimeException('exception message')); // "fails" with message "exception message"

// customize the failure message
Assert::try(
    fn() => throw new \RuntimeException('exception message'),
    'Tried to run the code but {exception} with message "{message}" was thrown.'
); // "fails" with message 'Tried to run the code but RuntimeException with message "exception message" was thrown.'
```

*Run* Assertions
----------------

[](#run-assertions)

`Assert::run()` executes a `callable`. A successful execution is considered a pass and if `Zenstruck\Assert\AssertionFailed` is thrown, it is a fail.

```
use Zenstruck\Assert;
use Zenstruck\Assert\AssertionFailed;

// failure
Assert::run(function(): void {
    if (true) {
        AssertionFailed::throw('This failed.');
    }
});

// pass
Assert::run(function(): void {
    if (false) {
        AssertionFailed::throw('This failed.');
    }
});
```

Expectation API
---------------

[](#expectation-api)

While the above assertions can be used to create *any* assertion, a simple, fluent, readable, expectation API is provided. This API is heavily inspired by [Pest PHP](https://pestphp.com/).

```
use Zenstruck\Assert;

// empty
Assert::that([])->isEmpty(); // pass
Assert::that(['foo'])->isEmpty(); // fail

Assert::that(null)->isNotEmpty(); // fail
Assert::that('value')->isNotEmpty(); // pass

// null
Assert::that(null)->isNull(); // pass
Assert::that('foo')->isNull(); // fail

Assert::that(null)->isNotNull(); // fail
Assert::that('value')->isNotNull(); // pass

// count
Assert::that([1, 2])->hasCount(2); // pass
Assert::that(new \ArrayIterator([1, 2, 3]))->hasCount(2); // fail

Assert::that(new \ArrayIterator([1, 2]))->doesNotHaveCount(5); // pass
Assert::that($countableObjectWithCountOf5)->doesNotHaveCount(5); // fail

// contains
Assert::that('foobar')->contains('foo'); // pass
Assert::that(['foo', 'bar'])->contains('foo'); // pass
Assert::that('foobar')->contains('baz'); // fail
Assert::that(['foo', 'bar'])->contains(6); // fail

Assert::that('foobar')->doesNotContain('baz'); // pass
Assert::that(new \ArrayIterator(['bar']))->doesNotContain('foo'); // pass
Assert::that('foobar')->doesNotContain('bar'); // fail
Assert::that(['foo', 'bar'])->doesNotContain('bar'); // fail

// array subsets
Assert::that(['foo' => 'bar'])->isSubsetOf(['foo' => 'bar', 'bar' => 'foo']); // pass
Assert::that(['foo' => 'bar'])->isSubsetOf(['bar' => 'foo']); // fail

Assert::that(['foo' => 'bar', 'bar' => 'foo'])->hasSubset(['foo' => 'bar']); // pass
Assert::that(['foo' => 'bar'])->hasSubset(['bar' => 'foo']); // fail

// array subset assertions can also be performed on non-associated arrays (lists/sets).
// Keep in mind that order does not matter.
Assert::that([
    'users' => [
        ['name' => 'user3', 'age' => 20],
        ['name' => 'user1'],
    ]
])->isSubsetOf([
    'users' => [
        ['name' => 'user1', 'age' => 25],
        ['name' => 'user2', 'age' => 23],
        ['name' => 'user3', 'age' => 20],
    ]
]); // pass

// also works with json strings that decode to arrays
Assert::that('[3, 1]')->isSubsetOf('[1, 2, 3]'); // pass

// equals (== comparison)
Assert::that('foo')->equals('foo'); // pass
Assert::that('6')->equals(6); // pass
Assert::that('foo')->equals('bar'); // fail
Assert::that(6)->equals(7); // fail

Assert::that('foo')->isNotEqualTo('bar'); // pass
Assert::that(6)->isNotEqualTo('6'); // fail

// is (=== comparison)
Assert::that('foo')->is('foo'); // pass
Assert::that(6)->is(6); // pass
Assert::that('foo')->is('bar'); // fail
Assert::that(6)->is('6'); // fail

Assert::that('foo')->isNot('foo'); // fail
Assert::that(6)->isNot(6); // fail
Assert::that('foo')->isNot('bar'); // pass
Assert::that(6)->isNot('6'); // pass

// boolean (===)
Assert::that(true)->isTrue(); // pass
Assert::that(false)->isTrue(); // fail
Assert::that(true)->isFalse(); // fail
Assert::that(false)->isFalse(); // pass

// boolean (==)
Assert::that(1)->isTruthy(); // pass
Assert::that(new \stdClass())->isTruthy(); // pass
Assert::that('text')->isTruthy(); // pass
Assert::that(null)->isTruthy(); // fail
Assert::that(0)->isFalsy(); // pass
Assert::that(null)->isFalsy(); // pass
Assert::that('')->isFalsy(); // pass
Assert::that(1)->isFalsy(); // fail

// instanceof
Assert::that($object)->isInstanceOf(Some::class);

Assert::that($object)->isNotInstanceOf(Some::class);

// greater than
Assert::that(2)->isGreaterThan(1); // pass
Assert::that(2)->isGreaterThan(1); // fail
Assert::that(2)->isGreaterThan(2); // fail

// greater than or equal to
Assert::that(2)->isGreaterThanOrEqualTo(1); // pass
Assert::that(2)->isGreaterThanOrEqualTo(1); // fail
Assert::that(2)->isGreaterThanOrEqualTo(2); // pass

// less than
Assert::that(3)->isLessThan(4); // pass
Assert::that(3)->isLessThan(2); // fail
Assert::that(3)->isLessThan(3); // fail

// less than or equal to
Assert::that(3)->isLessThanOrEqualTo(4); // pass
Assert::that(3)->isLessThanOrEqualTo(2); // fail
Assert::that(3)->isLessThanOrEqualTo(3); // pass
```

### Type Expectations

[](#type-expectations)

```
use Zenstruck\Assert;
use Zenstruck\Assert\Type;

Assert::that($something)->is(Type::bool());
Assert::that($something)->is(Type::int());
Assert::that($something)->is(Type::float());
Assert::that($something)->is(Type::numeric());
Assert::that($something)->is(Type::string());
Assert::that($something)->is(Type::callable());
Assert::that($something)->is(Type::iterable());
Assert::that($something)->is(Type::countable());
Assert::that($something)->is(Type::object());
Assert::that($something)->is(Type::resource());
Assert::that($something)->is(Type::array());
Assert::that($something)->is(Type::arrayList()); // [1, 2, 3] passes but ['foo' => 'bar'] does not
Assert::that($something)->is(Type::arrayAssoc()); // ['foo' => 'bar'] passes but [1, 2, 3] does not
Assert::that($something)->is(Type::arrayEmpty()); // [] passes but [1, 2, 3] does not
Assert::that($something)->is(Type::json()); // valid json string

// "Not's"
Assert::that($something)->isNot(Type::bool());
Assert::that($something)->isNot(Type::int());
Assert::that($something)->isNot(Type::float());
Assert::that($something)->isNot(Type::numeric());
Assert::that($something)->isNot(Type::string());
Assert::that($something)->isNot(Type::callable());
Assert::that($something)->isNot(Type::iterable());
Assert::that($something)->isNot(Type::countable());
Assert::that($something)->isNot(Type::object());
Assert::that($something)->isNot(Type::resource());
Assert::that($something)->isNot(Type::array());
Assert::that($something)->isNot(Type::arrayList());
Assert::that($something)->isNot(Type::arrayAssoc());
Assert::that($something)->isNot(Type::arrayEmpty());
Assert::that($something)->isNot(Type::json());
```

### Throws Expectation

[](#throws-expectation)

This expectation provides a nice API for exceptions. It is an alternative to PHPUnit's `expectException()` which has the following limitations:

1. Can only assert 1 exception is thrown per test.
2. Cannot make assertions on the exception itself (other than the message).
3. Cannot make post-exception assertions (think side effects).

```
use Zenstruck\Assert;

// the following can all be used within a single PHPUnit test

// fails if exception not thrown
// fails if exception is thrown but not instance of \RuntimeException
Assert::that(fn() => $code->thatThrowsException())->throws(\RuntimeException::class);

// fails if exception not thrown
// fails if exception is thrown but not instance of \RuntimeException
// fails if exception is thrown but exception message doesn't contain "some message"
Assert::that(fn() => $code->thatThrowsException())->throws(\RuntimeException::class, 'some message');

// a callable can be used for the expected exception. The first parameter's type
// hint is used as the expected exception and the callable is executed with the
// caught exception
//
// fails if exception not thrown
// fails if exception is thrown but not instance of CustomException
Assert::that(fn() => $code->thatThrowsException())->throws(
    function(CustomException $e) use ($database) {
        // make assertions on the exception
        Assert::that($e->getMessage())->contains('some message');
        Assert::that($e->getSomeValue())->is('value');

        // make side effect assertions
        Assert::true($database->userTableEmpty(), 'The user table is not empty');

        // If using within the context of a PHPUnit test, you can use standard PHPUnit assertions
        $this->assertStringContainsString('some message', $e->getMessage());
        $this->assertSame('value', $e->getSomeValue());
        $this->assertTrue($database->userTableEmpty());
    }
);
```

### Fluent Expectations

[](#fluent-expectations)

```
use Zenstruck\Assert;

// chain expectations on the same "value"
Assert::that(['foo', 'bar'])
    ->hasCount(2)
    ->contains('foo')
    ->contains('bar')
    ->doesNotContain('baz')
;

// start an additional expectation without breaking
Assert::that(['foo', 'bar'])
    ->hasCount(2)
    ->contains('foo')
    ->and('foobar') // start a new expectation with "foobar" as the new expectation value
    ->contains('bar')
;
```

`AssertionFailed` Exception
---------------------------

[](#assertionfailed-exception)

When triggering a failed assertion, it is important to provide a useful failure message to the user. The `AssertionFailed` exception has some features to help.

```
use Zenstruck\Assert\AssertionFailed;

// The `throw()` named constructor creates the exception and immediately throws it
AssertionFailed::throw('Some message');

// a second "context" parameter can be used as sprintf values for the message
AssertionFailed::throw('Expected "%s" but got "%s"', ['value 1', 'value 2']); // Expected "value 1" but got "value 2"

// when an associated array passed as the context parameter, the message is constructed
// with a simple template system
AssertionFailed::throw('Expected "{expected}" but got "{actual}"', [ // Expected "value 1" but got "value 2"
    'expected' => 'value 1',
    'actual' => 'value 2',
]);
```

**NOTES:**

1. When the message is constructed with context, non-scalar values are run through `get_debug_type()` and strings longer than *100* characters are trimmed. The full context is available via `AssertionFailed::context()`.
2. When using with PHPUnit, the full context is exported with the failure message if in *verbose-mode* (`--verbose|-v`).

Assertion Objects
-----------------

[](#assertion-objects)

Since `Zenstruck\Assert::run()` accepts any `callable`, complex assertions can be wrapped up into `invokable` objects:

```
use Zenstruck\Assert;
use Zenstruck\Assert\AssertionFailed;

class StringContains
{
    public function __construct(private string $haystack, private string $needle) {}

    public function __invoke(): void
    {
        if (!str_contains($this->haystack, $this->needle)) {
            AssertionFailed::throw(
                'Expected string "{haystack}" to contain "{needle}" but it did not.',
                get_object_vars($this)
            ]);
        }
    }
}

// use the above assertion:

// passes
Assert::run(new StringContains('quick brown fox', 'fox'));

// fails
Assert::run(new StringContains('quick brown fox', 'dog'));
```

Negatable Assertion Objects
---------------------------

[](#negatable-assertion-objects)

`Zenstruck\Assert` has a `not()` method that can be used with *Negatable*[Assertion Objects](#assertion-objects). This can be helpful to create custom assertions that can be easily negated. Let's convert the example above into a *Negatable Assertion Object*:

```
use Zenstruck\Assert;
use Zenstruck\Assert\AssertionFailed;
use Zenstruck\Assert\Assertion\Negatable;

class StringContains implements Negatable
{
    public function __construct(private string $haystack, private string $needle) {}

    public function __invoke(): void
    {
        if (!str_contains($this->haystack, $this->needle)) {
            AssertionFailed::throw(
                'Expected string "{haystack}" to contain "{needle}" but it did not.',
                get_object_vars($this)
            ]);
        }
    }

    public function notFailure(): AssertionFailed
    {
        return new AssertionFailed(
            'Expected string "{haystack}" to not contain "{needle}" but it did.',
            get_object_vars($this)
        );
    }
}

// use the above assertion:

// fails
Assert::not(new StringContains('quick brown fox', 'fox'));

// passes
Assert::not(new StringContains('quick brown fox', 'dog'));
```

###  Health Score

62

—

FairBetter than 99% of packages

Maintenance82

Actively maintained with recent releases

Popularity60

Solid adoption and visibility

Community24

Small or concentrated contributor base

Maturity67

Established project with proven stability

 Bus Factor1

Top contributor holds 89.7% 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 ~118 days

Recently: every ~201 days

Total

15

Last Release

92d ago

Major Versions

v0.2.2 → v1.0.02021-08-27

PHP version history (3 changes)v0.1.0PHP &gt;=7.2.5

v1.3.0PHP &gt;=8.0

v1.7.0PHP &gt;=8.1

### Community

Maintainers

![](https://www.gravatar.com/avatar/707369cc916e0ea1aacbf077dcba464f611cef879f024d8944311a54a15224b3?d=identicon)[kbond](/maintainers/kbond)

---

Top Contributors

[![kbond](https://avatars.githubusercontent.com/u/127811?v=4)](https://github.com/kbond "kbond (87 commits)")[![nikophil](https://avatars.githubusercontent.com/u/10139766?v=4)](https://github.com/nikophil "nikophil (8 commits)")[![brendt](https://avatars.githubusercontent.com/u/6905297?v=4)](https://github.com/brendt "brendt (1 commits)")[![raphaelstolt](https://avatars.githubusercontent.com/u/48225?v=4)](https://github.com/raphaelstolt "raphaelstolt (1 commits)")

---

Tags

phpunittestassertion

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/zenstruck-assert/health.svg)

```
[![Health](https://phpackages.com/badges/zenstruck-assert/health.svg)](https://phpackages.com/packages/zenstruck-assert)
```

###  Alternatives

[fr3d/swagger-assertions

Test your API requests and responses against your swagger definition

138850.9k5](/packages/fr3d-swagger-assertions)[ta-tikoma/phpunit-architecture-test

Methods for testing application architecture

10745.9M13](/packages/ta-tikoma-phpunit-architecture-test)[kenjis/ci-phpunit-test

An easier way to use PHPUnit with CodeIgniter 3.x

5861.2M3](/packages/kenjis-ci-phpunit-test)[php-mock/php-mock-phpunit

Mock built-in PHP functions (e.g. time()) with PHPUnit. This package relies on PHP's namespace fallback policy. No further extension is needed.

1718.2M399](/packages/php-mock-php-mock-phpunit)[ergebnis/phpunit-slow-test-detector

Provides facilities for detecting slow tests in phpunit/phpunit.

1468.1M72](/packages/ergebnis-phpunit-slow-test-detector)[elliotchance/concise

Concise is test framework for using plain English and minimal code, built on PHPUnit.

45223.8k4](/packages/elliotchance-concise)

PHPackages © 2026

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