-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
57049d8
commit 953b5a0
Showing
27 changed files
with
1,063 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,4 @@ parameters: | |
treatPhpDocTypesAsCertain: false | ||
paths: | ||
- src | ||
- tests |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace DR\PHPUnitExtensions\Mock; | ||
|
||
use PHPUnit\Framework\Constraint\Constraint; | ||
use PHPUnit\Framework\Constraint\IsEqual; | ||
use PHPUnit\Framework\Exception; | ||
use PHPUnit\Framework\ExpectationFailedException; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
class ConsecutiveParameters | ||
{ | ||
private int $callIndex = 0; | ||
|
||
/** | ||
* @param array<int, mixed|Constraint> $expectedArguments | ||
* | ||
* @throws Exception | ||
*/ | ||
public function __construct(private readonly array $expectedArguments) | ||
{ | ||
} | ||
|
||
/** | ||
* @throws ExpectationFailedException | ||
*/ | ||
public function evaluate(mixed $actualArgument): bool | ||
{ | ||
if (array_key_exists($this->callIndex, $this->expectedArguments) === false) { | ||
throw new ExpectationFailedException( | ||
sprintf('consecutive was called %d times, but only received arguments for %d', $this->callIndex + 1, count($this->expectedArguments)) | ||
); | ||
} | ||
|
||
// take the argument for the correct call index | ||
$expectedArgument = $this->expectedArguments[$this->callIndex]; | ||
|
||
// evaluate the argument against the expected argument | ||
$constraint = $expectedArgument instanceof Constraint ? $expectedArgument : new IsEqual($expectedArgument); | ||
$constraint->evaluate($actualArgument); | ||
|
||
++$this->callIndex; | ||
|
||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace DR\PHPUnitExtensions\Mock; | ||
|
||
use InvalidArgumentException; | ||
use PHPUnit\Framework\Constraint\Callback; | ||
|
||
/** | ||
* @param array<mixed> $firstInvocationArguments A list of arguments for each invocation that will be asserted against. | ||
* Will fail on: too many invocations or if the argument doesn't match for each invocation. | ||
* @param array<mixed> $secondInvocationArguments | ||
* @param array<mixed> ...$expectedArgumentList | ||
* | ||
* @note Full qualified name to appease to phpstorm inspection gods | ||
* phpcs:ignore | ||
* @return \PHPUnit\Framework\Constraint\Callback<mixed>[] | ||
* @example <code>->with(...consecutive([5, 'foo'], [6, 'bar']))</code> | ||
*/ | ||
function consecutive(array $firstInvocationArguments, array $secondInvocationArguments, array ...$expectedArgumentList): array | ||
{ | ||
array_unshift($expectedArgumentList, $secondInvocationArguments); | ||
array_unshift($expectedArgumentList, $firstInvocationArguments); | ||
|
||
// reorganize arguments per argument index | ||
$argumentsByIndex = []; | ||
foreach ($expectedArgumentList as $invocation => $expectedArguments) { | ||
if (count($expectedArguments) === 0) { | ||
throw new InvalidArgumentException('consecutive() is expecting at least 1 or more arguments for invocation #' . ((int)$invocation + 1)); | ||
} | ||
|
||
foreach ($expectedArguments as $index => $argument) { | ||
$argumentsByIndex[$index][] = $argument; | ||
} | ||
} | ||
|
||
$callbacks = []; | ||
foreach ($argumentsByIndex as $arguments) { | ||
$constraint = new ConsecutiveParameters($arguments); | ||
$callbacks[] = new Callback(static fn($actualArgument): bool => $constraint->evaluate($actualArgument)); | ||
} | ||
|
||
return $callbacks; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace DR\PHPUnitExtensions\Symfony; | ||
|
||
use PHPUnit\Framework\MockObject\Exception; | ||
use PHPUnit\Framework\MockObject\MockObject; | ||
use PHPUnit\Framework\TestCase; | ||
use Symfony\Component\EventDispatcher\EventDispatcher; | ||
use Symfony\Component\Form\Extension\Core\DataMapper\DataMapper; | ||
use Symfony\Component\Form\Form; | ||
use Symfony\Component\Form\FormBuilder; | ||
use Symfony\Component\Form\FormFactoryBuilder; | ||
use Symfony\Component\Validator\Constraint; | ||
use Symfony\Component\Validator\ConstraintValidator; | ||
use Symfony\Component\Validator\Context\ExecutionContextInterface; | ||
use Symfony\Component\Validator\Exception\UnexpectedTypeException; | ||
use Symfony\Component\Validator\Violation\ConstraintViolationBuilder; | ||
|
||
/** | ||
* @template V of ConstraintValidator | ||
* @template C of Constraint | ||
*/ | ||
abstract class AbstractConstraintValidatorTestCase extends TestCase | ||
{ | ||
protected const IGNORE_INVALID_VALUE = 'UT_IGNORE_INVALID_VALUE'; | ||
|
||
/** @var V */ | ||
protected ConstraintValidator $validator; | ||
|
||
/** @var C */ | ||
protected Constraint $constraint; | ||
|
||
protected ExecutionContextInterface&MockObject $executionContext; | ||
protected ConstraintViolationBuilder&MockObject $violationBuilder; | ||
protected Form $form; | ||
|
||
/** | ||
* @return V | ||
*/ | ||
abstract protected function getValidator(): ConstraintValidator; | ||
|
||
/** | ||
* @return C | ||
*/ | ||
abstract protected function getConstraint(): Constraint; | ||
|
||
/** | ||
* @throws Exception | ||
*/ | ||
protected function setUp(): void | ||
{ | ||
parent::setUp(); | ||
$this->violationBuilder = $this->createMock(ConstraintViolationBuilder::class); | ||
$this->executionContext = $this->createMock(ExecutionContextInterface::class); | ||
|
||
$this->validator = $this->getValidator(); | ||
$this->constraint = $this->getConstraint(); | ||
|
||
$this->validator->initialize($this->executionContext); | ||
} | ||
|
||
protected function initRootForm(): Form | ||
{ | ||
$formBuilder = new FormBuilder('foobar', null, new EventDispatcher(), (new FormFactoryBuilder())->getFormFactory()); | ||
$formBuilder->setCompound(true); | ||
$formBuilder->setDataMapper(new DataMapper()); | ||
$this->form = new Form($formBuilder); | ||
|
||
return $this->form; | ||
} | ||
|
||
/** | ||
* @throws Exception | ||
*/ | ||
protected function assertHandlesIncorrectConstraintType(mixed $value = null): void | ||
{ | ||
$this->expectException(UnexpectedTypeException::class); | ||
$this->validator->validate($value, $this->createMock(Constraint::class)); | ||
} | ||
|
||
protected function expectNoViolations(): void | ||
{ | ||
$this->executionContext->expects(static::never())->method('buildViolation'); | ||
$this->executionContext->expects(static::never())->method('addViolation'); | ||
} | ||
|
||
/** | ||
* Expect a violation to be added using addViolation method. | ||
* e.g. $this->context->addViolation($constraint->message); | ||
* | ||
* @param array<int|string, mixed> $parameters | ||
*/ | ||
protected function expectViolation(string $message, array $parameters = []): void | ||
{ | ||
$this->executionContext->expects(static::once())->method('addViolation')->with($message, $parameters); | ||
} | ||
|
||
/** | ||
* Expect a violation to be created using the violation builder. | ||
* e.g. $this->context->buildViolation($constraint->message)->atPath('price')->setInvalidValue($price)->addViolation(); | ||
* | ||
* @param array<int|string, mixed> $parameters | ||
*/ | ||
protected function expectViolationViaBuilder( | ||
string $message, | ||
array $parameters = [], | ||
?string $atPath = null, | ||
mixed $invalidValue = self::IGNORE_INVALID_VALUE | ||
): void { | ||
$this->executionContext->expects(static::once())->method('buildViolation')->with($message, $parameters)->willReturn($this->violationBuilder); | ||
if ($atPath !== null) { | ||
$this->violationBuilder->expects(static::once())->method('atPath')->with($atPath)->willReturnSelf(); | ||
} | ||
if ($invalidValue !== self::IGNORE_INVALID_VALUE) { | ||
$this->violationBuilder->expects(static::once())->method('setInvalidValue')->with($invalidValue)->willReturnSelf(); | ||
} | ||
$this->violationBuilder->expects(static::once())->method('addViolation'); | ||
} | ||
} |
Oops, something went wrong.