Skip to content

Commit

Permalink
Closes #5551
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastianbergmann committed Oct 28, 2023
1 parent 1ac044c commit 2efe0ed
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 8 deletions.
1 change: 1 addition & 0 deletions ChangeLog-10.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ All notable changes of the PHPUnit 10.5 release series are documented in this fi
### Added

* [#5532](https://github.com/sebastianbergmann/phpunit/issues/5532): `#[IgnoreDeprecations]` attribute to ignore `E_(USER_)DEPRECATED` issues on test class and test method level
* [#5551](https://github.com/sebastianbergmann/phpunit/issues/5551): Support for omitting parameter default values for `willReturnMap()`

[10.5.0]: https://github.com/sebastianbergmann/phpunit/compare/10.4...main
36 changes: 33 additions & 3 deletions src/Framework/MockObject/ConfigurableMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,29 @@ final class ConfigurableMethod
* @psalm-var non-empty-string
*/
private readonly string $name;

/**
* @psalm-var array<int, mixed>
*/
private readonly array $defaultParameterValues;

/**
* @psalm-var non-negative-int
*/
private readonly int $numberOfParameters;
private readonly Type $returnType;

/**
* @psalm-param non-empty-string $name
* @psalm-param array<int, mixed> $defaultParameterValues
* @psalm-param non-negative-int $numberOfParameters
*/
public function __construct(string $name, Type $returnType)
public function __construct(string $name, array $defaultParameterValues, int $numberOfParameters, Type $returnType)
{
$this->name = $name;
$this->returnType = $returnType;
$this->name = $name;
$this->defaultParameterValues = $defaultParameterValues;
$this->numberOfParameters = $numberOfParameters;
$this->returnType = $returnType;
}

/**
Expand All @@ -39,6 +53,22 @@ public function name(): string
return $this->name;
}

/**
* @psalm-return array<int, mixed>
*/
public function defaultParameterValues(): array
{
return $this->defaultParameterValues;
}

/**
* @psalm-return non-negative-int
*/
public function numberOfParameters(): int
{
return $this->numberOfParameters;
}

public function mayReturn(mixed $value): bool
{
return $this->returnType->isAssignable(Type::fromValue($value, false));
Expand Down
8 changes: 7 additions & 1 deletion src/Framework/MockObject/Generator/Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -730,7 +730,13 @@ private function generateCodeForTestDoubleClass(string $type, bool $mockObject,

foreach ($mockMethods->asArray() as $mockMethod) {
$mockedMethods .= $mockMethod->generateCode();
$configurable[] = new ConfigurableMethod($mockMethod->methodName(), $mockMethod->returnType());

$configurable[] = new ConfigurableMethod(
$mockMethod->methodName(),
$mockMethod->defaultParameterValues(),
$mockMethod->numberOfParameters(),
$mockMethod->returnType(),
);
}

/** @psalm-var trait-string[] $traits */
Expand Down
59 changes: 56 additions & 3 deletions src/Framework/MockObject/Generator/MockMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/
namespace PHPUnit\Framework\MockObject\Generator;

use function count;
use function explode;
use function implode;
use function is_object;
Expand Down Expand Up @@ -54,6 +55,16 @@ final class MockMethod
private readonly bool $static;
private readonly ?string $deprecation;

/**
* @psalm-var array<int, mixed>
*/
private readonly array $defaultParameterValues;

/**
* @psalm-var non-negative-int
*/
private readonly int $numberOfParameters;

/**
* @throws ReflectionException
* @throws RuntimeException
Expand Down Expand Up @@ -94,6 +105,8 @@ public static function fromReflection(ReflectionMethod $method, bool $callOrigin
$modifier,
self::methodParametersForDeclaration($method),
self::methodParametersForCall($method),
self::methodParametersDefaultValues($method),
count($method->getParameters()),
(new ReflectionMapper)->fromReturnType($method),
$reference,
$callOriginalMethod,
Expand All @@ -115,6 +128,8 @@ public static function fromName(string $className, string $methodName, bool $clo
'public',
'',
'',
[],
0,
new UnknownType,
'',
false,
Expand All @@ -124,17 +139,21 @@ public static function fromName(string $className, string $methodName, bool $clo
}

/**
* @param class-string $className
* @param non-empty-string $methodName
* @psalm-param class-string $className
* @psalm-param non-empty-string $methodName
* @psalm-param array<int, mixed> $defaultParameterValues
* @psalm-param non-negative-int $numberOfParameters
*/
private function __construct(string $className, string $methodName, bool $cloneArguments, string $modifier, string $argumentsForDeclaration, string $argumentsForCall, Type $returnType, string $reference, bool $callOriginalMethod, bool $static, ?string $deprecation)
private function __construct(string $className, string $methodName, bool $cloneArguments, string $modifier, string $argumentsForDeclaration, string $argumentsForCall, array $defaultParameterValues, int $numberOfParameters, Type $returnType, string $reference, bool $callOriginalMethod, bool $static, ?string $deprecation)
{
$this->className = $className;
$this->methodName = $methodName;
$this->cloneArguments = $cloneArguments;
$this->modifier = $modifier;
$this->argumentsForDeclaration = $argumentsForDeclaration;
$this->argumentsForCall = $argumentsForCall;
$this->defaultParameterValues = $defaultParameterValues;
$this->numberOfParameters = $numberOfParameters;
$this->returnType = $returnType;
$this->reference = $reference;
$this->callOriginalMethod = $callOriginalMethod;
Expand Down Expand Up @@ -215,6 +234,22 @@ public function returnType(): Type
return $this->returnType;
}

/**
* @psalm-return array<int, mixed>
*/
public function defaultParameterValues(): array
{
return $this->defaultParameterValues;
}

/**
* @psalm-return non-negative-int
*/
public function numberOfParameters(): int
{
return $this->numberOfParameters;
}

/**
* Returns the parameters of a function or method.
*
Expand Down Expand Up @@ -329,4 +364,22 @@ private static function exportDefaultValue(ReflectionParameter $parameter): stri
}
// @codeCoverageIgnoreEnd
}

/**
* @psalm-return array<int, mixed>
*/
private static function methodParametersDefaultValues(ReflectionMethod $method): array
{
$result = [];

foreach ($method->getParameters() as $i => $parameter) {
if (!$parameter->isDefaultValueAvailable()) {
continue;
}

$result[$i] = $parameter->getDefaultValue();
}

return $result;
}
}
43 changes: 42 additions & 1 deletion src/Framework/MockObject/Runtime/Builder/InvocationMocker.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@
use function array_key_exists;
use function array_map;
use function array_merge;
use function array_pop;
use function assert;
use function count;
use function is_string;
use function range;
use function strtolower;
use PHPUnit\Framework\Constraint\Constraint;
use PHPUnit\Framework\InvalidArgumentException;
Expand Down Expand Up @@ -117,7 +120,45 @@ public function willReturnReference(mixed &$reference): self

public function willReturnMap(array $valueMap): self
{
$stub = new ReturnValueMap($valueMap);
$method = $this->configuredMethod();

assert($method instanceof ConfigurableMethod);

$numberOfParameters = $method->numberOfParameters();
$defaultValues = $method->defaultParameterValues();
$hasDefaultValues = !empty($defaultValues);

$_valueMap = [];

foreach ($valueMap as $mapping) {
$numberOfConfiguredParameters = count($mapping) - 1;

if ($numberOfConfiguredParameters === $numberOfParameters || !$hasDefaultValues) {
$_valueMap[] = $mapping;

continue;
}

$_mapping = [];
$returnValue = array_pop($mapping);

foreach (range(0, $numberOfParameters - 1) as $i) {
if (isset($mapping[$i])) {
$_mapping[] = $mapping[$i];

continue;
}

if (isset($defaultValues[$i])) {
$_mapping[] = $defaultValues[$i];
}
}

$_mapping[] = $returnValue;
$_valueMap[] = $_mapping;
}

$stub = new ReturnValueMap($_valueMap);

return $this->will($stub);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\TestFixture\MockObject;

interface InterfaceWithMethodThatHasDefaultParameterValues
{
public function doSomething(int $a, int $b = 1): int;
}
21 changes: 21 additions & 0 deletions tests/unit/Framework/MockObject/TestDoubleTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use PHPUnit\Framework\TestCase;
use PHPUnit\TestFixture\MockObject\ExtendableClassWithCloneMethod;
use PHPUnit\TestFixture\MockObject\InterfaceWithMethodThatExpectsObject;
use PHPUnit\TestFixture\MockObject\InterfaceWithMethodThatHasDefaultParameterValues;
use PHPUnit\TestFixture\MockObject\InterfaceWithNeverReturningMethod;
use PHPUnit\TestFixture\MockObject\InterfaceWithReturnTypeDeclaration;
use stdClass;
Expand Down Expand Up @@ -137,6 +138,26 @@ final public function testMethodCanBeConfiguredToReturnValuesBasedOnArgumentMapp
$this->assertSame(4, $double->doSomethingElse(3));
}

final public function testMethodWithDefaultParameterValuesCanBeConfiguredToReturnValuesBasedOnArgumentMapping(): void
{
$double = $this->createTestDouble(InterfaceWithMethodThatHasDefaultParameterValues::class);

$double->method('doSomething')->willReturnMap([[1, 2, 3], [4, 5, 6]]);

$this->assertSame(3, $double->doSomething(1, 2));
$this->assertSame(6, $double->doSomething(4, 5));
}

final public function testMethodWithDefaultParameterValuesCanBeConfiguredToReturnValuesBasedOnArgumentMappingThatOmitsDefaultValues(): void
{
$double = $this->createTestDouble(InterfaceWithMethodThatHasDefaultParameterValues::class);

$double->method('doSomething')->willReturnMap([[1, 2], [3, 4]]);

$this->assertSame(2, $double->doSomething(1));
$this->assertSame(4, $double->doSomething(3));
}

final public function testMethodCanBeConfiguredToReturnValuesUsingCallback(): void
{
$double = $this->createTestDouble(InterfaceWithReturnTypeDeclaration::class);
Expand Down

0 comments on commit 2efe0ed

Please sign in to comment.