expect/peridot-expect-plugin
Peridot plugin that adds Expect-style assertions to your specs, so you can write expressive expect($value)->to... checks inside Peridot tests. Install with Composer and enable the plugin in your Peridot configuration for readable, behavior-driven PHP testing.
What this package actually is. expect/peridot-expect-plugin is a thin adapter — a Peridot plugin that wires the Expect expectation library into Peridot's spec runner so you can write expect($x)->to->... chains instead of Peridot's built-in assert* helpers. Given the 1-star / 0-dependents footprint, treat it as glue code you may end up owning: the entire plugin is small enough to read in one sitting, and that is where you should start.
First 15 minutes — before installing:
composer show peridot-php/peridot expect/* and confirm the installed Peridot version and PHP constraint. Both Peridot and Expect are PHP 5/7-era projects; verify PHP 8 compatibility first, not after wiring it up.vendor/expect/peridot-expect-plugin/src/ — expect one class (a plugin/registrar) plus maybe a composer.json files autoload entry for the expect() function. That single file tells you the exact class name and registration API, which is authoritative over any README.ls vendor/expectation-php/expect/src/Matcher/ (or wherever the Expect source landed). This is your real API surface; matcher vocabulary differs between Expect versions.Install:
composer require --dev expect/peridot-expect-plugin
Pin it in composer.json ("expect/peridot-expect-plugin": "0.x.y" exact, or a tight constraint) — with this little adoption you do not want a surprise minor bump.
Register in peridot.php. Peridot's config file returns a closure receiving the runner's Evenement\EventEmitterInterface. The plugin registers itself against that emitter; the shape is roughly:
<?php
// peridot.php
return function ($emitter) {
$plugin = new Expect\Plugin\PeridotPlugin(); // <- confirm class from src/
$plugin->register($emitter); // <- confirm method from src/
};
First use case. Pick one small spec file and rewrite assertions as expectations to see whether the reporter output is what your team is used to:
describe('Cart', function () {
beforeEach(function () {
$this->cart = new Cart();
});
it('starts empty', function () {
expect($this->cart->count())->to->equal(0);
});
it('adds an item', function () {
$this->cart->add(new Item('book', 1200));
expect($this->cart->count())->to->equal(1);
expect($this->cart->total())->to->equal(1200);
});
});
Run just that file: vendor/bin/peridot spec/Cart.spec.php. If failures render as Peridot failures (red spec, correct message) rather than raw uncaught exceptions, the plugin is wired correctly and you can roll it out.
Replace, don't mix. The main day-to-day value is readability: expect($order->total())->to->equal(4999) reads better than assertSame(4999, $order->total()) and produces domain-flavoured failure text. Decide per project — not per file — whether specs use Expect or Peridot's built-ins. Mixed styles make it ambiguous which failure format you're debugging.
Favour the negative and the compound for triage. Failures are cheaper to read when the assertion states intent:
it('rejects an expired coupon', function () {
$coupon = Coupon::expired();
expect($coupon->isValid())->to->be(false);
expect(fn () => $this->cart->apply($coupon))
->to->throw(InvalidArgumentException::class);
});
Wrap expectations in domain helpers. This is the highest-leverage pattern for a team: keep the Expect chain out of the spec body so you can swap assertion libraries later without touching 200 specs.
// spec/support/expectations.php (required from peridot.php)
function expectUser($user) {
return expect($user);
}
Then expectUser($user)->to->be->active() — the spec reads as English, and the coupling to Expect lives in one file.
Custom matchers are the extension point. When a domain concept recurs ("is a valid invoice", "is idempotent"), implement the Expect matcher interface (typically matches($actual) + describe()/describeNegation()) rather than repeating three assertions in ten specs. Register them once in peridot.php so every spec sees them. This is also the cleanest way to get a good failure message.
Data providers + expectations. Peridot's dataset/provider hooks pair well with expectations — the chain keeps the per-row assertion compact:
it('normalises phone numbers', function ($input, $expected) {
expect(Phone::normalise($input))->to->equal($expected);
})->with([
['+44 7911 123456', '447911123456'],
['(555) 010-9999', '5550109999'],
]);
Use it as a migration bridge. If a team is fluent in Expect/RSpec-style syntax but stuck on Peridot, this plugin lets you keep the idiom while gradually porting to PHPUnit/Pest. Treat it as an on-ramp, not a destination — see the maintenance caveat below.
CI integration. Nothing special: Peridot's exit code drives CI. Do verify one deliberately failing spec in CI so you know failures surface as failures and not as errors (see Gotchas) — a suite that exits 0 because exceptions were swallowed is worse than no suite.
Maintenance risk is the headline gotcha. One star, zero dependents, an old framework, an old assertion library. Practical mitigations, in order of preference:
composer.lock (it's require-dev, so it doesn't ship — but CI must install from the lock).spec/support/ExpectPlugin.php) if upstream disappears or breaks on a PHP upgrade. That removes the supply-chain worry entirely at a cost of ~30 lines.PHP 8 breakage is likely. Both Peridot and Expect date from the PHP 5.6–7.x era. Expect dynamic properties, create_function, missing return types, and deprecation notices in the matcher internals. If you see deprecations, silence them per-file rather than globally, and pin an error_reporting in your CI bootstrap. Test the whole suite on the target PHP before adopting.
Function redeclaration. The expect() function almost certainly comes from a composer.json files autoload entry. If another dev dependency (or your own support file) defines expect(), you get a fatal Cannot redeclare expect(). If you need both, use the namespaced variant or alias yours. Also check files autoload ordering: your helper functions must be loaded after Expect's.
Failures vs. errors. Expect's failure model is its own (a matcher result / dedicated exception), not Peridot's AssertionException. If the plugin's reporter hook isn't registered or registers after the reporter, a failed expectation can surface as an "error" with a stack trace instead of a clean "failure" with a message. Symptom: green/red counts look wrong, or you get stack traces for plain assertion mismatches. Fix by registering the plugin early in peridot.php and re-running one failing spec.
peridot.php runs on every invocation. Don't do work there — no container boot, no DB connections, no file scans. Register the plugin, register matchers, require your support file, done.
Negation and chaining quirks. not is stateful in a fluent chain; don't assume expect($x)->to->not->equal(1)->and->... reads the way you think. Prefer one assertion per expect() call in specs — you get the first failure only anyway, and Peridot won't tell you about the second one you wrote.
Debugging checklist when a spec "just doesn't work":
vendor/bin/peridot path/to/file.spec.php for a single file; add -d/debug output if the version supports it.var_dump() the matcher result or the actual value before the chain — if expect() itself throws, the problem is registration, not your assertion.peridot.php (it's discovered from the working directory; running Peridot from a subdirectory silently skips it).php -r 'var_dump(class_exists("Expect\\Plugin\\PeridotPlugin"));'.Don't leak it into production. Keep it require-dev and keep expect/matcher helpers out of src/. The temptation, once you like the syntax, is to use it for runtime invariant checks — resist it; you'll ship a dev dependency's failure semantics into production.
Extension points worth knowing: custom matchers (best ROI), the Peridot emitter events (peridot.start, test-failure hooks) for cross-cutting output, and a custom reporter if the default failure formatting doesn't show Expect's messages well enough. All three are small, readable files — which is the real advantage of a package this thin.
How can I help you explore Laravel packages today?