Skip to content

PropertyCheck ​

Rasuvaeff\PropertyTesting\PhpUnit\PropertyCheck

Class — Package: property-testing-phpunit — Source — Version: v1.0.0

Fluent builder mapping the engine's structured PropertyResult onto PHPUnit: a pass counts one assertion, every failing outcome surfaces as one AssertionFailedError carrying the engine failure as previous — the message is the engine's own (seed, original and shrunk arguments, shrink statistics included).

Environment parity with the Testo adapter: PROPERTY_RUNS overrides every run count, PROPERTY_SEED seeds unseeded properties (an explicit PhpUnit\seed() wins), PROPERTY_VERBOSE traces every run, and PROPERTY_DB enables the regression corpus — an explicit seed() disables replay, exactly like an attribute seed does under Testo. An Assume::that() discard is a discarded run inside the property, never a skipped PHPUnit test.

Constructor ​

php
__construct(
    PHPUnit\Framework\TestCase $testCase,
    string $id,
    string $name,
    array<array-key,mixed> $generators,
    bool $idDerivedIndirectly,
)
ParameterTypeDefaultDescription
$testCasePHPUnit\Framework\TestCaserequired
$idstringrequired
$namestringrequired
$generatorsarray<array-key,mixed>requiredThe PhpUnit\PropertyTesting::forAll() map as written; PhpUnit\check() rejects anything in it that is not an ArbitraryInterface keyed by a parameter name.
$idDerivedIndirectlyboolrequired

Methods ​

currentId() ​

php
currentId(): string

The id the corpus is keyed by, as currently resolved.

id() ​

php
id(string $id): self

Names the property, replacing the id derived from the calling method.

The id keys the regression corpus entry and every event, so it has to name the same property tomorrow. Derived from the caller it does — for a test method. From a closure it cannot: PHP 8.3 calls every closure of a class {closure}, so two properties in one file share a corpus key and overwrite each other's counterexample, and from 8.4 the name carries a line number that an edit above shifts, orphaning yesterday's entry. Neither throws — the corpus simply stops replaying the failure it exists to replay. Pest's it()/test() bodies are the common case.

The name given here is used verbatim, and it is also the property's display name, so one string identifies it in the corpus, in the events and in the printed output.

throws() ​

php
throws(non-empty-string $exceptionClass): self

The exception class every trial must throw. A trial that throws it passes; one that throws anything else, or returns normally, fails and shrinks like any other counterexample.

This is the property-level replacement for expectException(), which cannot work inside a check() closure: the executor observes every throw before PHPUnit's own expectation mechanism does. A failed assertion is never the expected throw, and a class it is an instance of (\Exception, \Throwable) is refused here. A skip (markTestSkipped(), markTestIncomplete()) and an Assume::that() discard are still what they are — the environment's verdict about the run, never a pass earned by throwing.

runs() ​

php
runs(int $runs): self

Number of successful checks to complete; discarded runs do not count.

seed() ​

php
seed(int $seed): self

Pins the random phase's seed. A pinned seed also disables regression replay, so the pinned reproducibility wins over the corpus.

maxShrinks() ​

php
maxShrinks(int $maxShrinks): self

Cap on accepted shrink steps; 0 disables shrinking.

maxDiscards() ​

php
maxDiscards(int $maxDiscards): self

Maximum discarded runs before the property gives up.

timeoutMs() ​

php
timeoutMs(int $timeoutMs): self

Wall-clock deadline for a single run, in milliseconds.

budgetMs() ​

php
budgetMs(int $budgetMs): self

Wall-clock budget for the whole random phase, in milliseconds.

shrink() ​

php
shrink(Runner\ShrinkMode $shrink): self

How hard to minimise a counterexample: ShrinkMode::Full (the default), ShrinkMode::Off to report the input as generated, or ShrinkMode::Bounded together with shrinkBudgetMs().

shrinkBudgetMs() ​

php
shrinkBudgetMs(int $shrinkBudgetMs): self

Wall-clock budget for the shrink descent, in milliseconds — the one knob here that costs determinism: how far the descent gets depends on how long the body takes, so the same seed can minimise differently on a fast and a slow machine. It answers "the descent hung", not "reproduce this exactly".

phases() ​

php
phases(list<\Runner\Phase> $phases): self

Which stages this run performs, in run order — a subset trades coverage for time on purpose (replaying only the examples and the corpus turns a minutes-long suite into a seconds-long pull-request gate).

derandomize() ​

php
derandomize(bool $derandomize = true): self

Derives an unset seed from the property id instead of drawing it at random, so the same property on the same code always selects the same inputs. An explicit seed() still wins.

edgeCases() ​

php
edgeCases(Runner\EdgeCases $edgeCases): self

Whether the numeric generators keep biasing toward their boundary values (EdgeCases::Mixin, the default) or generate uniformly (EdgeCases::None).

Turn them off when the edges are what this property cannot use — a body discarding 0, a range end that violates a precondition — so the discard budget stops paying for one run in five.

exhaustive() ​

php
exhaustive(bool $exhaustive = true): self

Walk the whole parameter domain instead of sampling it, when every generator has a finite domain (Enumerable) and the product fits exhaustiveBudget(); otherwise the phase samples and a warning says why. runs() is ignored when it walks. PROPERTY_EXHAUSTIVE turns it on for the whole suite.

exhaustiveBudget() ​

php
exhaustiveBudget(int $exhaustiveBudget): self

The largest domain exhaustive() walks; 10 000 by default.

flakyReplays() ​

php
flakyReplays(int $flakyReplays): self

Re-executions of the minimised counterexample after the descent — one that passes marks the counterexample flaky, with a Flaky: line in the failure. 2 by default; 0 disables the check.

searchRuns() ​

php
searchRuns(int $searchRuns): self

Bodies the targeted search may execute after the random phase, for a body that calls Target::maximize()/minimize(): the best-scoring inputs are mutated one parameter at a time. 0 (the default) performs no search. PROPERTY_SEARCH_RUNS overrides it for the whole suite.

path() ​

php
path(string $path): self

Replays the shrink descent of an earlier failure, as reported by CounterExample::$path, instead of searching for it again. It needs the seed of the run that produced it — the steps mean nothing against another one — so check() refuses a path without a seed() or a PROPERTY_SEED.

auto() ​

php
auto(bool $auto = true): self

Derive a generator from the closure's signature for every parameter the PropertyTesting::forAll() map does not cover — the @param psalm type when the closure has a docblock (int<1, 300> beats a bare int), the native type otherwise, and an error naming the parameter for anything unreadable. The map becomes the overrides and may be partial — or empty: a fully-typed property needs no map at all.

Strictly opt-in, never the default: a bare int or float derives its full native domain, and only the property's author knows whether that is the intended one. There is deliberately no PROPERTY_AUTO environment variable — the environment dials the suite, while this changes what one property's arguments mean.

examples() ​

php
examples(list<list> $examples): self

Fixed positional argument tuples run before the random phase. A failing example short-circuits and is reported unshrunk — it is already minimal.

listeners() ​

php
listeners(PropertyListener $listeners): self

Observers of the run's engine events, notified in the given order.

output() ​

php
output(resource $stdout, resource $stderr): self

Redirects the distribution report, discard warning and verbose trace — for tests of this adapter itself.

clock() ​

php
clock(Runner\Clock $clock): self

Replaces the runner's source of elapsed time — for tests of this adapter itself. The Testo adapter takes the same seam on its interceptor; without it the timeoutMs and budgetMs branches can only be reached by really waiting, which is not a test.

check() ​

php
check(callable $property): void

Runs the property. The closure's parameter names select the generators.