marcin-orlowski/phpunit-extra-asserts
A collection of extra assertions for PHPUnit that helps make test suites more expressive and readable. Add clearer, reusable checks for common test scenarios, reduce custom assertion code, and get more informative failures in PHPUnit-based projects.
Install as a dev dependency — this is a test-only library and must never ship to production:
composer require --dev marcin-orlowski/phpunit-extra-asserts
Where to look first. The repo is small, so read it end-to-end before wiring it in: src/ contains the trait(s) of assertions, and the README lists the available methods. Because the package has very low adoption (3 stars, no dependents), treat the source as the source of truth rather than blog posts. Check composer.json for the supported PHP and PHPUnit version constraints before assuming it drops into an existing suite.
Wire it in once. The package ships assertions as a trait. Add it to your application's base TestCase so every test inherits the helpers without per-file boilerplate:
// tests/TestCase.php
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use MarcinOrlowski\PhpunitExtraAsserts\ExtraAsserts; // verify exact FQCN in src/
abstract class TestCase extends BaseTestCase
{
use ExtraAsserts;
}
First use case. The highest-value entry point is string/JSON shape assertions on API responses, where PHPUnit's native assertions force you into multi-line assertTrue(...) blocks. Instead of:
$this->assertTrue(str_starts_with($response->json('id'), 'usr_'));
$this->assertTrue(is_string($response->json('email')));
you get a single semantic assertion with a clear failure message:
$this->assertStringStartsWith('usr_', $response->json('id'));
$this->assertStringIsJson($response->getContent());
Pick one test that currently has three or four chained boolean assertions and convert it. That is the cheapest way to evaluate whether the package earns a place in your suite.
Trait at the base class, not per-test. Applying the trait in tests/TestCase.php keeps individual test files clean and means a future package upgrade is a single-file change. If you already have a large suite with several base classes (Feature, Unit, Browser), apply it to a shared parent trait or to each base class — do not sprinkle use ExtraAsserts; across hundreds of test files.
Assertion wrappers for domain concepts. The package's real leverage is as a substrate for your own assertions. Build a tests/Assertions/ trait on top of it:
trait AssertsOrderShape
{
public function assertOrderPayload(array $order): void
{
$this->assertArrayHasKeys(['id', 'total', 'currency', 'lines'], $order);
$this->assertStringStartsWith('ord_', $order['id']);
}
}
Then a single assertOrderPayload() in each endpoint test replaces six low-level assertions. The extra-asserts package supplies the primitives; your trait supplies the vocabulary.
API and JSON response testing. Combine framework assertions with the package's string checks. Laravel's assertJsonStructure verifies keys exist; the extra string helpers verify the format of the values inside them. This two-layer split (structure via Laravel, scalar shape via extra-asserts) reads well in reviews and produces focused failure output.
File and directory fixtures. For tests that generate files (exports, PDFs, log writers), the filesystem assertions are far more expressive than $this->assertTrue(file_exists(...)). Use them in tearDown() as well as in the body of the test to assert cleanup actually happened.
Data-provider driven suites. Extra assertions pair naturally with #[DataProvider] methods that feed many string/array shapes into one test. Each case gets a one-line assertion and a legible failure message naming the offending value.
Migration path for removed PHPUnit assertions. assertArraySubset was deprecated and removed from PHPUnit. If you have legacy tests relying on it, the package's array helpers are a lower-friction replacement than rewriting every call site — but prefer native assertArrayHasKey loops for new code where they are clear enough.
Pair with expectException carefully. Use the package's assertion methods inside try/catch blocks only when you need to inspect both the exception and the response state; otherwise the native expectException API is cleaner and less prone to false passes.
Check PHPUnit version overlap. PHPUnit has steadily absorbed extra assertions into core — assertStringContainsString, assertStringStartsWith, assertStringEndsWith, and assertIsArray are all native in modern versions. Before adopting a package method, confirm it is not simply shadowing core; if it does, prefer the native one so your suite stays portable and your failure messages stay familiar to other developers.
Trait method name collisions. If two traits in your TestCase define the same method, PHP fatals with "trait method has not been applied, because there are collisions." If you hit this, resolve with insteadof in the base class rather than renaming — and document the resolution so the next person does not rediscover it.
Low adoption means low bus factor. Three stars and no dependents is a real risk signal. Pin the exact version in composer.json, and if a critical assertion is load-bearing, copy the method body into your own assertions trait. It is a handful of lines of assertTrue under the hood — owning it costs almost nothing and removes the upgrade risk.
Failure messages are less polished than core. Extra assertions implemented as thin wrappers often produce messages that echo the raw values without the contextual formatting PHPUnit generates natively. When a test fails in CI, developers reading the log will be slower to diagnose. For your most-run tests, prefer native assertions even when the extra one is shorter.
Loose comparison traps. Assertions that internally use == or assertEquals semantics will surprise you with type juggling — assertArraysEqual([1], ['1']) may pass. For anything security- or money-adjacent, use the package's strict variants if they exist, or fall back to assertSame with an expected value you construct explicitly.
Static analysis and autocompletion. Because the methods arrive via trait, PHPStan and IDEs only see them if the trait is actually resolved on the class. Add @mixin ExtraAsserts to your base TestCase docblock if static analysis complains, and run composer dump-autoload after install so the trait resolves.
Do not assert on generated messages. If you build a helper that wraps these assertions and emits custom messages, never assert on the message text in your own meta-tests — it couples your suite to the package's internal wording and will break on a patch release.
Extension point. The package likely exposes a base test-case class or a helper for custom assertions. If your team needs a domain-specific assertion library, extend the package's trait rather than forking it — that way upgrades to the upstream trait flow through, and you keep the small surface area you actually depend on clearly delineated.
How can I help you explore Laravel packages today?