Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Extract iterables matching logic to a standalone class
- Loading branch information
Showing
10 changed files
with
452 additions
and
33 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
<?php | ||
|
||
namespace spec\PhpSpec\Matcher\Iterate; | ||
|
||
use PhpSpec\Matcher\Iterate\SubjectElementDoesNotMatchException; | ||
use PhpSpec\Matcher\Iterate\SubjectHasLessElementsException; | ||
use PhpSpec\Matcher\Iterate\SubjectHasMoreElementsException; | ||
use PhpSpec\ObjectBehavior; | ||
|
||
/** | ||
* @author Kamil Kokot <kamil.kokot@lakion.com> | ||
*/ | ||
final class IterablesMatcherSpec extends ObjectBehavior | ||
{ | ||
function it_should_throw_an_invalid_argument_exception_if_subject_is_not_iterable() | ||
{ | ||
$this | ||
->shouldThrow(new \InvalidArgumentException('Subject value should be an array or implement \Traversable.')) | ||
->during('match', ['not iterable', []]) | ||
; | ||
|
||
$this | ||
->shouldThrow(new \InvalidArgumentException('Subject value should be an array or implement \Traversable.')) | ||
->during('match', [9, []]) | ||
; | ||
|
||
$this | ||
->shouldThrow(new \InvalidArgumentException('Subject value should be an array or implement \Traversable.')) | ||
->during('match', [new \stdClass(), []]) | ||
; | ||
} | ||
|
||
function it_should_throw_an_invalid_argument_exception_if_expected_value_is_not_iterable() | ||
{ | ||
$this | ||
->shouldThrow(new \InvalidArgumentException('Expected value should be an array or implement \Traversable.')) | ||
->during('match', [[], 'not iterable']) | ||
; | ||
|
||
$this | ||
->shouldThrow(new \InvalidArgumentException('Expected value should be an array or implement \Traversable.')) | ||
->during('match', [[], 9]) | ||
; | ||
|
||
$this | ||
->shouldThrow(new \InvalidArgumentException('Expected value should be an array or implement \Traversable.')) | ||
->during('match', [[], new \stdClass()]) | ||
; | ||
} | ||
|
||
function it_should_throw_an_exception_if_subject_has_less_elements_than_expected() | ||
{ | ||
$this | ||
->shouldThrow(new SubjectHasLessElementsException()) | ||
->during('match', [['a' => 'b'], ['a' => 'b', 'c' => 'd']]) | ||
; | ||
} | ||
|
||
function it_should_throw_an_exception_if_subject_has_more_elements_than_expected() | ||
{ | ||
$this | ||
->shouldThrow(new SubjectHasMoreElementsException()) | ||
->during('match', [['a' => 'b', 'c' => 'd'], ['a' => 'b']]) | ||
; | ||
} | ||
|
||
function it_should_throw_an_exception_if_subject_element_does_not_match_the_expected_one() | ||
{ | ||
$this | ||
->shouldThrow(new SubjectElementDoesNotMatchException(0, 'a', 'b', 'a', 'c')) | ||
->during('match', [['a' => 'b'], ['a' => 'c']]) | ||
; | ||
|
||
$this | ||
->shouldThrow(new SubjectElementDoesNotMatchException(0, 'a', 'b', 'c', 'b')) | ||
->during('match', [['a' => 'b'], ['c' => 'b']]) | ||
; | ||
|
||
$this | ||
->shouldThrow(new SubjectElementDoesNotMatchException(1, 'c', 'd', 'c', 'e')) | ||
->during('match', [['a' => 'b', 'c' => 'd'], ['a' => 'b', 'c' => 'e']]) | ||
; | ||
} | ||
|
||
function it_should_not_throw_any_exception_if_subject_iterates_as_expected() | ||
{ | ||
$this | ||
->shouldNotThrow() | ||
->during('match', [['a' => 'b', 'c' => 'd'], ['a' => 'b', 'c' => 'd']]) | ||
; | ||
|
||
$this | ||
->shouldNotThrow() | ||
->during('match', [['a' => 'b', 'c' => 'd'], new \ArrayIterator(['a' => 'b', 'c' => 'd'])]) | ||
; | ||
|
||
$this | ||
->shouldNotThrow() | ||
->during('match', [new \ArrayIterator(['a' => 'b', 'c' => 'd']), ['a' => 'b', 'c' => 'd']]) | ||
; | ||
|
||
$this | ||
->shouldNotThrow() | ||
->during('match', [['a' => 'b', 'c' => 'd'], new \ArrayObject(['a' => 'b', 'c' => 'd'])]) | ||
; | ||
|
||
$this | ||
->shouldNotThrow() | ||
->during('match', [new \ArrayObject(['a' => 'b', 'c' => 'd']), ['a' => 'b', 'c' => 'd']]) | ||
; | ||
|
||
$this | ||
->shouldNotThrow() | ||
->during('match', [$this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd']), ['a' => 'b', 'c' => 'd']]) | ||
; | ||
|
||
$this | ||
->shouldNotThrow() | ||
->during('match', [['a' => 'b', 'c' => 'd'], $this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd'])]) | ||
; | ||
} | ||
|
||
/** | ||
* @param array $array | ||
* | ||
* @return \Generator | ||
*/ | ||
private function createGeneratorReturningArray(array $array) | ||
{ | ||
foreach ($array as $key => $value) { | ||
yield $key => $value; | ||
} | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
spec/PhpSpec/Matcher/Iterate/SubjectElementDoesNotMatchExceptionSpec.php
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,33 @@ | ||
<?php | ||
|
||
namespace spec\PhpSpec\Matcher\Iterate; | ||
|
||
use PhpSpec\ObjectBehavior; | ||
use Prophecy\Argument; | ||
|
||
class SubjectElementDoesNotMatchExceptionSpec extends ObjectBehavior | ||
{ | ||
function let() | ||
{ | ||
$this->beConstructedWith(42, 'subject key', 'subject value', 'expected key', 'expected value'); | ||
} | ||
|
||
function it_is_a_runtime_exception() | ||
{ | ||
$this->shouldHaveType(\RuntimeException::class); | ||
} | ||
|
||
function it_has_a_predefined_message() | ||
{ | ||
$this->getMessage()->shouldReturn('Subject element does not match with expected element.'); | ||
} | ||
|
||
function it_contains_the_details_of_matched_element() | ||
{ | ||
$this->getElementNumber()->shouldReturn(42); | ||
$this->getSubjectKey()->shouldReturn('subject key'); | ||
$this->getSubjectValue()->shouldReturn('subject value'); | ||
$this->getExpectedKey()->shouldReturn('expected key'); | ||
$this->getExpectedValue()->shouldReturn('expected value'); | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
spec/PhpSpec/Matcher/Iterate/SubjectHasLessElementsExceptionSpec.php
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,19 @@ | ||
<?php | ||
|
||
namespace spec\PhpSpec\Matcher\Iterate; | ||
|
||
use PhpSpec\ObjectBehavior; | ||
use Prophecy\Argument; | ||
|
||
final class SubjectHasLessElementsExceptionSpec extends ObjectBehavior | ||
{ | ||
function it_is_a_length_exception() | ||
{ | ||
$this->shouldHaveType(\LengthException::class); | ||
} | ||
|
||
function it_has_a_predefined_message() | ||
{ | ||
$this->getMessage()->shouldReturn('Subject has less elements than expected.'); | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
spec/PhpSpec/Matcher/Iterate/SubjectHasMoreElementsExceptionSpec.php
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,19 @@ | ||
<?php | ||
|
||
namespace spec\PhpSpec\Matcher\Iterate; | ||
|
||
use PhpSpec\ObjectBehavior; | ||
use Prophecy\Argument; | ||
|
||
final class SubjectHasMoreElementsExceptionSpec extends ObjectBehavior | ||
{ | ||
function it_is_a_length_exception() | ||
{ | ||
$this->shouldHaveType(\LengthException::class); | ||
} | ||
|
||
function it_has_a_predefined_message() | ||
{ | ||
$this->getMessage()->shouldReturn('Subject has more elements than expected.'); | ||
} | ||
} |
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 |
---|---|---|
@@ -0,0 +1,79 @@ | ||
<?php | ||
|
||
namespace PhpSpec\Matcher\Iterate; | ||
|
||
final class IterablesMatcher | ||
{ | ||
/** | ||
* @param array|\Traversable $subject | ||
* @param array|\Traversable $expected | ||
* | ||
* @throws \InvalidArgumentException | ||
* @throws SubjectElementDoesNotMatchException | ||
* @throws SubjectHasLessElementsException | ||
* @throws SubjectHasMoreElementsException | ||
*/ | ||
public function match($subject, $expected) | ||
{ | ||
if (!$this->isIterable($subject)) { | ||
throw new \InvalidArgumentException('Subject value should be an array or implement \Traversable.'); | ||
} | ||
|
||
if (!$this->isIterable($expected)) { | ||
throw new \InvalidArgumentException('Expected value should be an array or implement \Traversable.'); | ||
} | ||
|
||
$expectedIterator = $this->createIteratorFromIterable($expected); | ||
|
||
$count = 0; | ||
foreach ($subject as $subjectKey => $subjectValue) { | ||
if (!$expectedIterator->valid()) { | ||
throw new SubjectHasMoreElementsException(); | ||
} | ||
|
||
if ($subjectKey !== $expectedIterator->key() || $subjectValue !== $expectedIterator->current()) { | ||
throw new SubjectElementDoesNotMatchException( | ||
$count, | ||
$subjectKey, | ||
$subjectValue, | ||
$expectedIterator->key(), | ||
$expectedIterator->current() | ||
); | ||
} | ||
|
||
$expectedIterator->next(); | ||
++$count; | ||
} | ||
|
||
if ($expectedIterator->valid()) { | ||
throw new SubjectHasLessElementsException(); | ||
} | ||
} | ||
|
||
/** | ||
* @param mixed $variable | ||
* | ||
* @return bool | ||
*/ | ||
private function isIterable($variable) | ||
{ | ||
return is_array($variable) || $variable instanceof \Traversable; | ||
} | ||
|
||
/** | ||
* @param array|\Traversable $expected | ||
* | ||
* @return \Iterator | ||
*/ | ||
private function createIteratorFromIterable($expected) | ||
{ | ||
if (is_array($expected)) { | ||
return new \ArrayIterator($expected); | ||
} | ||
|
||
$iterator = new \IteratorIterator($expected); | ||
$iterator->rewind(); | ||
|
||
return $iterator; | ||
} | ||
} |
Oops, something went wrong.