Skip to content
This repository has been archived by the owner on Jul 8, 2023. It is now read-only.

Commit

Permalink
Allow multiple arguments to generator answer returns and throws. Closes
Browse files Browse the repository at this point in the history
  • Loading branch information
ezzatron committed Mar 17, 2016
1 parent 11472e7 commit bc4afd1
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 16 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

## Next release

- **[NEW]** Implemented generator stubs ([#11]).
- **[NEW]** Implemented generator stubs ([#11], [#140]).
- **[IMPROVED]** More default values for built-in return types ([#138], [#139]).

[#11]: https://github.com/eloquent/phony/issues/11
[#138]: https://github.com/eloquent/phony/issues/138
[#139]: https://github.com/eloquent/phony/pull/139
[#140]: https://github.com/eloquent/phony/issues/140

## 0.8.0 (2016-02-12)

Expand Down
91 changes: 79 additions & 12 deletions doc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
- [Invoking callables]
- [Stubbing generators]
- [Yielding from a generator]
- [Yielding individual values from a generator]
- [Yielding multiple values from a generator]
- [Returning values from a generator]
- [Returning arguments from a generator]
- [Returning the "self" value from a generator]
Expand Down Expand Up @@ -2566,7 +2568,7 @@ Add a yielded value to the answer.

*If no arguments are supplied, the stub will yield like `yield;`.*

*See [Yielding from a generator].*
*See [Yielding individual values from a generator].*

<a name="generatorAnswer.yieldsFrom" />

Expand All @@ -2579,13 +2581,13 @@ Add a set of yielded values to the answer.
*The `$values` argument can be an array, an iterator, or even another
generator.*

*See [Yielding from a generator].*
*See [Yielding multiple values from a generator].*

<a name="generatorAnswer.returns" />

----

> *[stub][stub-api]* $generatorAnswer->[**returns**](#generatorAnswer.returns)($value = null)
> *[stub][stub-api]* $generatorAnswer->[**returns**](#generatorAnswer.returns)($value = null, ...$additionalValues)
End the generator by returning a value.

Expand Down Expand Up @@ -2620,7 +2622,7 @@ End the generator by returning the self value.

----

> *[stub][stub-api]* $generatorAnswer->[**throws**](#generatorAnswer.throws)($exception = null)
> *[stub][stub-api]* $generatorAnswer->[**throws**](#generatorAnswer.throws)($exception = null, ...$additionalExceptions)
End the generator by throwing an exception.

Expand Down Expand Up @@ -3447,11 +3449,12 @@ echo json_encode($values); // outputs '[]'
```

The result of [`generates()`](#stub.generates) is a [generator answer]. This
object can be used to further customize the behavior of the generator to be
returned. See the subsequent headings for details of these customizations.
object can be used to further customize the behavior of the generator. See the
subsequent headings for details of these customizations.

When a method is called on the generator answer that "ends" the answer (by
returning or throwing), the original stub is returned, allowing continued
Certain methods, such as [`returns()`](#generatorAnswer.returns), or
[`throws()`](#generatorAnswer.throws), mark the "end" of generator answer. When
a generator answer is "ended", the original stub is returned, allowing continued
stubbing in a fluent manner:

```php
Expand All @@ -3478,7 +3481,7 @@ echo $resultC instanceof Generator ? 'true' : 'false'; // outputs 'true'
#### Yielding from a generator

Keys and values to be yielded can be passed directly to
[`generates()`](#stub.generates) as an array:
[`generates()`](#stub.generates) as any traversable value:

```php
$stub = stub()
Expand All @@ -3495,8 +3498,11 @@ echo json_encode($valuesA); // outputs '["a","b","c","d"]'
echo json_encode($valuesB); // outputs '{"e":"f","g":"h"}'
```

Alternatively, [`yields()`](#generatorAnswer.yields) can be used when yields
need to be interleaved with other actions:
##### Yielding individual values from a generator

For more complicated generator behavior stubbing,
[`yields()`](#generatorAnswer.yields) can be used to interleave yields with
other actions:

```php
$count = 0;
Expand Down Expand Up @@ -3542,6 +3548,8 @@ $values = iterator_to_array($stub()); // consume the generator
echo json_encode($values); // outputs '{"a":"b","0":"c","1":null}'
```

##### Yielding multiple values from a generator

To yield a set of values from an array, an iterator, or another generator, use
[`yieldsFrom()`](#generatorAnswer.yieldsFrom):

Expand Down Expand Up @@ -3595,6 +3603,35 @@ iterator_to_array($generator); // consume the generator
echo $generator->getReturn(); // outputs 'a'
```

Calling [`returns()`](#generatorAnswer.returns) with multiple arguments allows
for easy specification of the generator return value on subsequent invocations.
For example, the two following stubs behave the same:

```php
$stubA = stub()
->generates()->returns('x', 'y');

$generatorA = $stubA();
$generatorB = $stubA();
iterator_to_array($generatorA);
iterator_to_array($generatorB);

echo $generatorA->getReturn(); // outputs 'x'
echo $generatorB->getReturn(); // outputs 'y'

$stubB = stub()
->generates()->returns('x')
->generates()->returns('y');

$generatorA = $stubB();
$generatorB = $stubB();
iterator_to_array($generatorA);
iterator_to_array($generatorB);

echo $generatorA->getReturn(); // outputs 'x'
echo $generatorB->getReturn(); // outputs 'y'
```

Note that attempting to return anything other than `null` will result in an
exception unless the current runtime supports generator return expressions. For
older runtimes, it is perfectly valid to call
Expand Down Expand Up @@ -3631,7 +3668,7 @@ echo $generatorC->getReturn(); // outputs 'z'

#### Returning the "self" value from a generator

The stub [self value] can be return from a generator by using
The stub [self value] can be returned from a generator by using
[`returnsSelf()`](#generatorAnswer.returnsSelf) on any [generator answer]:

```php
Expand Down Expand Up @@ -3664,6 +3701,34 @@ iterator_to_array($generatorA); // throws $exception
iterator_to_array($generatorB); // throws a generic exception
```

Calling [`throws()`](#generatorAnswer.throws) with multiple arguments allows for
easy specification of the thrown exception on subsequent invocations. For
example, the two following stubs behave the same:

```php
$exceptionA = new RuntimeException('You done goofed.');
$exceptionB = new RuntimeException('Consequences will never be the same.');

$stubA = stub()
->generates()->throws($exceptionA, $exceptionB);

$generatorA = $stubA();
$generatorB = $stubA();

iterator_to_array($generatorA); // throws $exceptionA
iterator_to_array($generatorB); // throws $exceptionB

$stubB = stub()
->generates()->throws($exceptionA)
->generates()->throws($exceptionB);

$generatorA = $stubB();
$generatorB = $stubB();

iterator_to_array($generatorA); // throws $exceptionA
iterator_to_array($generatorB); // throws $exceptionB
```

#### Generator iterations that perform multiple actions

Stubbed generators can perform multiple actions as part of a single iteration.
Expand Down Expand Up @@ -7291,6 +7356,8 @@ For the full copyright and license information, please view the [LICENSE file].
[verifying values received by spies]: #verifying-values-received-by-spies
[when to use the "equal to" matcher]: #when-to-use-the-equal-to-matcher
[yielding from a generator]: #yielding-from-a-generator
[yielding individual values from a generator]: #yielding-individual-values-from-a-generator
[yielding multiple values from a generator]: #yielding-multiple-values-from-a-generator

<!-- Shortcut references -->

Expand Down
42 changes: 42 additions & 0 deletions src/Stub/Answer/Builder/GeneratorAnswerBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -337,12 +337,20 @@ public function yieldsFrom($values)
* End the generator by returning a value.
*
* @param mixed $value The return value.
* @param mixed ...$additionalValues Additional return values for subsequent invocations.
*
* @return StubInterface The stub.
* @throws RuntimeException If the current runtime does not support the supplied return value.
*/
public function returns($value = null)
{
$argumentCount = func_num_args();
$copies = array();

for ($i = 1; $i < $argumentCount; ++$i) {
$copies[$i] = clone $this;
}

if (
$value instanceof InstanceHandleInterface &&
$value->isAdaptable()
Expand All @@ -363,6 +371,13 @@ public function returns($value = null)
}
// @codeCoverageIgnoreEnd

for ($i = 1; $i < $argumentCount; ++$i) {
$this->stub
->doesWith($copies[$i]->answer(), array(), true, true, false);

$copies[$i]->returns(func_get_arg($i));
}

return $this->stub;
}

Expand Down Expand Up @@ -415,11 +430,19 @@ public function returnsSelf()
* End the generator by throwing an exception.
*
* @param Exception|Error|string|null $exception The exception, or message, or null to throw a generic exception.
* @param Exception|Error|string ...$additionalExceptions Additional exceptions, or messages, for subsequent invocations.
*
* @return StubInterface The stub.
*/
public function throws($exception = null)
{
$argumentCount = func_num_args();
$copies = array();

for ($i = 1; $i < $argumentCount; ++$i) {
$copies[$i] = clone $this;
}

if (is_string($exception)) {
$exception = new Exception($exception);
} elseif (
Expand All @@ -433,6 +456,13 @@ public function throws($exception = null)

$this->exception = $exception;

for ($i = 1; $i < $argumentCount; ++$i) {
$this->stub
->doesWith($copies[$i]->answer(), array(), true, true, false);

$copies[$i]->throws(func_get_arg($i));
}

return $this->stub;
}

Expand Down Expand Up @@ -468,6 +498,18 @@ public function answer()
// @codeCoverageIgnoreEnd
}

/**
* Clone this builder.
*/
public function __clone()
{
// explicitly break references
foreach (get_object_vars($this) as $property => $value) {
unset($this->$property);
$this->$property = $value;
}
}

private $stub;
private $isGeneratorReturnSupported;
private $invocableInspector;
Expand Down
2 changes: 2 additions & 0 deletions src/Stub/Answer/Builder/GeneratorAnswerBuilderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ public function yieldsFrom($values);
* @api
*
* @param mixed $value The return value.
* @param mixed ...$additionalValues Additional return values for subsequent invocations.
*
* @return StubInterface The stub.
* @throws RuntimeException If the current runtime does not support the supplied return value.
Expand Down Expand Up @@ -181,6 +182,7 @@ public function returnsSelf();
* @api
*
* @param Exception|Error|string|null $exception The exception, or message, or null to throw a generic exception.
* @param Exception|Error|string ...$additionalExceptions Additional exceptions, or messages, for subsequent invocations.
*
* @return StubInterface The stub.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,20 @@ public function testReturnsWithValue()
$this->markTestSkipped('Requires generator return values.');
}

$this->assertSame($this->stub, $this->subject->yields('a')->yields('b')->returns('c'));

$generator = call_user_func($this->answer, $this->self, $this->arguments);

$this->assertSame(array('a', 'b'), iterator_to_array($generator));
$this->assertSame('c', $generator->getReturn());
}

public function testReturnsWithInstanceHandleValue()
{
if (!$this->featureDetector->isSupported('generator.return')) {
$this->markTestSkipped('Requires generator return values.');
}

$adaptable = Phony::mock();
$this->assertSame($this->stub, $this->subject->yields('a')->yields('b')->returns($adaptable));

Expand All @@ -382,18 +396,25 @@ public function testReturnsWithValue()
$this->assertSame($adaptable->mock(), $generator->getReturn());
}

public function testReturnsWithInstanceHandleValue()
public function testReturnsWithMulitpleValues()
{
if (!$this->featureDetector->isSupported('generator.return')) {
$this->markTestSkipped('Requires generator return values.');
}

$this->assertSame($this->stub, $this->subject->yields('a')->yields('b')->returns('c'));
$this->stub->doesWith($this->answer, array(), true, true, false);

$generator = call_user_func($this->answer, $this->self, $this->arguments);
$this->assertSame($this->stub, $this->subject->yields('a')->yields('b')->returns('c', 'd'));

$generator = call_user_func($this->stub);

$this->assertSame(array('a', 'b'), iterator_to_array($generator));
$this->assertSame('c', $generator->getReturn());

$generator = call_user_func($this->stub);

$this->assertSame(array('a', 'b'), iterator_to_array($generator));
$this->assertSame('d', $generator->getReturn());
}

public function testReturnsFailureValueNotSupported()
Expand Down Expand Up @@ -563,6 +584,43 @@ public function testThrowsWithException()
$this->assertSame($exception, $actual);
}

public function testThrowsWithMultipleExceptions()
{
$this->stub->doesWith($this->answer, array(), true, true, false);
$exceptionA = new Exception('a');
$exceptionB = new Exception('b');
$this->subject->throws($exceptionA, $exceptionB);
$generator = call_user_func($this->stub);
$actual = null;

try {
iterator_to_array($generator);
} catch (Exception $actual) {
}

$this->assertSame($exceptionA, $actual);

$generator = call_user_func($this->stub);
$actual = null;

try {
iterator_to_array($generator);
} catch (Exception $actual) {
}

$this->assertSame($exceptionB, $actual);

$generator = call_user_func($this->stub);
$actual = null;

try {
iterator_to_array($generator);
} catch (Exception $actual) {
}

$this->assertSame($exceptionB, $actual);
}

public function testThrowsWithMessage()
{
$exception = new Exception();
Expand Down
Loading

0 comments on commit bc4afd1

Please sign in to comment.