Skip to content

WhenBuilder ​

Rasuvaeff\Understudy\WhenBuilder

Class — Package: rasuvaeff/understudy — Source — Version: v1.1.0-1-g6ed023f

Type parameters:

  • TReturn of mixed = mixed

Configures what a stubbed call does.

TReturn is the return type of the specified method. Plain Psalm and PHPStan cannot infer it from the closure on their own — understudy-psalm fills it in, and until then the parameter stays mixed, which is why the template is declared here rather than added later: a published signature cannot grow one without changing its contract.

Not final, and the one class here that is not: ExpectBuilder extends it to add the cardinality verbs, and the two are one fluent vocabulary rather than two. For everyone else it is closed by contract, not by keyword: subclassing is not supported, the protected readonly Expectation it carries is an @internal type that may change in any release, and a subclass reading it is on its own. The keyword is missing only because closing the class would mean duplicating every action verb on ExpectBuilder — and a @final tag is not an option either, since Psalm would then refuse ExpectBuilder itself.

Constructor ​

php
__construct(
    Expectation\Expectation $expectation,
    \Expectation\ReturnContract|null $contract = NULL,
)
ParameterTypeDefaultDescription
$expectationExpectation\Expectationrequired
$contract\Expectation\ReturnContract|nullNULLwhat the specified method may answer with; null only where no signature is known

Methods ​

returns() ​

php
returns(\TReturn $values): static

Returns each value in turn across successive calls, then keeps returning the last one.

throws() ​

php
throws(Throwable $error): static

Throws this exact instance on the call — the same object every time the link answers, which is what a test holding a reference to it expects.

throwsWith() ​

php
throwsWith(callable $build): static

Throws an exception built from the call itself, one per call.

  • $build — builds the exception from the call it answers

The shape throws() cannot express: an exception that carries what the call was made with — new PublishException($message, outboxMessage: $call->args[0]). A throwing answers() closure does the same thing and stays supported; this reads as what it is at the call site.

php
when(fn () => $publisher->publish(Arg::any()))
    ->throwsWith(fn (Invocation $call) => new PublishException(
        message: 'Publish failed',
        outboxMessage: $call->arg('message'),
    ));

answers() ​

php
answers(callable $answer): static

Computes the return value for each matching call.

then() ​

php
then(): static

Opens the next link of the chain, so the verb that follows describes what the next call does:

php
when(fn () => $breaker->call($operation))
    ->returns('ok')
    ->then()->throws(new ConnectionLost());

Once the chain runs out, its last link keeps answering.

where() ​

php
where(callable $guard, non-empty-string $description = 'where(…)'): static

Narrows the specification by a predicate over the whole argument list — the one shape a positional matcher cannot express: a constraint over two or more arguments together.

  • $guard — accepts the argument list the call carried, or refuses it
  • $description — how the guard reads in a failure message
php
when(fn () => $ledger->transfer($from, Arg::any()))
    ->where(fn ($from, $to) => $from <= $to)
    ->returns(true);

The closure receives the arguments the call carried, positionally, so it reads like the method's own signature; it runs wherever matching runs — dispatch, verify(), the by-reference slot decision — and a call the positions accept but the guard refuses simply does not match: a broader expectation may still answer it. That also means the guard runs inside the code under test, and an exception it raises is the test's own error.

Works on expect() the same way, and verify() takes one through its where: parameter. One guard per expectation; a second where() is refused. In a failure message the guard renders as where(…) — or as the $description given, which is how a test names what the constraint means:

php
->where(fn ($w, $h) => $w / $h === 2, description: 'where(16:9)')

times() ​

php
times(int<0, max> $minimum, int<0, max>|null $maximum = NULL): static

Makes the call count part of what verifyAll() checks. Without it a stub is permission, not a claim.

With one argument the count is exact; with two it is a range, and a null maximum means no upper bound.