sebastianbergmann/phpunit · error · ExpectationFailedException
Parameter count for invocation %s is too low.
Error message
Parameter count for invocation %s is too low.
What it means
PHPUnit throws this when an actual call to the mocked method passed fewer arguments than the number of constraints you gave to ->with(). Parameters::doVerify() compares the invocation's argument list against the configured constraints; if the real call supplies fewer values (typically because trailing parameters are optional and were omitted), verification fails. PHPUnit even appends a hint when the single constraint was anything(): it suspects you wrote ->with($this->anything()) to mean 'any parameters', which actually means 'exactly one parameter of any value'.
Source
Thrown at src/Framework/MockObject/Runtime/Rule/Parameters.php:123
$invocation = $this->invocation;
$invocationParameters = $invocation->parameters();
if (count($invocationParameters) < count($this->parameters)) {
$message = 'Parameter count for invocation %s is too low.';
// The user called `->with($this->anything())`, but may have meant
// `->withAnyParameters()`.
//
// @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/199
if (count($this->parameters) === 1 &&
$this->parameters[0]::class === IsAnything::class) {
$message .= "\nTo allow 0 or more parameters with any value, omit ->with() or use ->withAnyParameters() instead.";
}
$this->incrementAssertionCount();
throw new ExpectationFailedException(
sprintf($message, $invocation->toString()),
);
}
$parameters = $this->parameters($invocation);
foreach ($this->parameters as $i => $parameter) {
$other = null;
if ($parameter instanceof Callback && $parameter->isVariadic()) {
$other = $invocationParameters;
} elseif (isset($invocationParameters[$i])) {
$other = $invocationParameters[$i];
}
$this->incrementAssertionCount();
$parameter->evaluate(View on GitHub (pinned to f123cdb2a2)
Solutions
- If any number of arguments is acceptable, replace ->with(...) with ->withAnyParameters() (the failure message itself suggests this for the single-anything() case).
- Otherwise make the constraints match the real arity: one constraint per argument the code actually passes, using $this->anything() only for arguments you do not care about but that ARE passed.
- Check the doubled method's signature for optional/variadic parameters and mirror that shape in with().
- If the code under test should pass the argument, fix that call site rather than loosening the expectation.
- For variadic matching pass a variadic Callback constraint so one constraint covers the remaining arguments.
Example fix
// before
$logger->expects($this->once())->method('log')->with($this->anything());
$logger->log(); // called with zero arguments
// after: explicitly allow zero or more arguments of any value
$logger->expects($this->once())->method('log')->withAnyParameters(); Defensive patterns
Strategy: validation
Validate before calling
// Before writing with(), check the doubled method's signature and count the // arguments the SUT actually passes (optional args may be omitted): $ref = new ReflectionMethod($sut::class, 'log'); $required = $ref->getNumberOfRequiredParameters(); // If the call may pass zero args, do not use with() at all: // ->withAnyParameters() accepts 0 or more.
Type guard
// Narrow before configuring: does the method accept the arity you are about to constrain?
function acceptsArity(object|string $classOrInstance, string $method, int $arity): bool
{
$r = new ReflectionMethod($classOrInstance, $method);
return $arity <= $r->getNumberOfParameters()
&& $arity >= $r->getNumberOfRequiredParameters();
} Prevention
- Use ->withAnyParameters() when argument count can vary; ->with($this->anything()) means exactly one argument.
- Mirror the real call's arity in with(): one constraint per actually-passed argument, anything() only for passed-but-ignored values.
- Re-check expectations after changing signatures with optional or variadic parameters.
- Use a variadic Callback constraint to absorb trailing arguments in one constraint.
When it happens
Trigger: ->with($this->anything(), $this->anything()) on a method whose optional second argument the SUT omits; ->with($this->anything()) on a zero-argument call — the message then suggests omitting ->with() or using ->withAnyParameters(); default-valued parameters not passed explicitly by the code under test.
Common situations: Calling $mock->log() with no args while the test expects with('msg'); variadic or optional parameters added/removed in a signature refactor; PHPUnit 9-to-10 migration where with($this->anything()) no longer tolerates a different arity; test doubles for methods with many defaults where production code passes only the first argument.
Related errors
- Doubled method does not exist.
- Expected invocation at least %d time%s but it occurred %d ti
- Expected invocation at least once but it never occurred.
- Expected invocation at most %d time%s but it occurred %d tim
- Method was expected to be called %d time%s, actually called
AI-assisted analysis of sebastianbergmann/phpunit@f123cdb2a2 (2026-08-23).
Data as JSON: /api/errors/272a7c0d9500237d.
Report an issue: GitHub.