Skip to content

Commit

Permalink
Add ability for Expected methods to take in Stubs
Browse files Browse the repository at this point in the history
or ConsecutiveMap
  • Loading branch information
mikk150 committed Mar 6, 2024
1 parent 4fcad2c commit 5ff459b
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 10 deletions.
16 changes: 13 additions & 3 deletions src/Stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
use PHPUnit\Framework\MockObject\Generator\Generator;
use PHPUnit\Framework\MockObject\MockObject as PHPUnitMockObject;
use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount;
use PHPUnit\Framework\MockObject\Stub\ConsecutiveCalls;
use PHPUnit\Framework\MockObject\Stub\ReturnCallback;
use PHPUnit\Framework\MockObject\Stub\ReturnStub;
use PHPUnit\Framework\MockObject\Stub\Stub as MockObjectStub;
use PHPUnit\Framework\TestCase as PHPUnitTestCase;
use PHPUnit\Runner\Version as PHPUnitVersion;
use ReflectionClass;
Expand Down Expand Up @@ -522,7 +522,7 @@ protected static function bindParameters($mock, array $params)
$mock
->expects($marshaler->getMatcher())
->method($param)
->will(new ReturnCallback($marshaler->getValue()));
->will($marshaler->getValue());
} elseif ($value instanceof Closure) {
$mock
->expects(new AnyInvokedCount)
Expand All @@ -533,7 +533,13 @@ protected static function bindParameters($mock, array $params)
$mock
->expects(new AnyInvokedCount)
->method($param)
->will(new ConsecutiveCalls($consecutiveMap->getMap()));
->will($consecutiveMap);
} elseif ($value instanceof MockObjectStub) {
$stub = $value;
$mock
->expects(new AnyInvokedCount)
->method($param)
->will($stub);
} else {
$mock
->expects(new AnyInvokedCount)
Expand Down Expand Up @@ -607,6 +613,10 @@ protected static function getMethodsToReplace(ReflectionClass $reflection, array
* $user->getName(); //sam
* $user->getName(); //amy
* ```
*
* It also takes in PHPUnit Stubs.
*
* $user = Stub::make('User', ['getName' => Stub::consecutive(new ReturnCallback([fn() => 'david', fn() => 'emma', fn() => 'sam', fn() => 'amy']))]);
*/
public static function consecutive(): ConsecutiveMap
{
Expand Down
31 changes: 28 additions & 3 deletions src/Stub/ConsecutiveMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,45 @@

namespace Codeception\Stub;

use PHPUnit\Framework\MockObject\Invocation;
use PHPUnit\Framework\MockObject\Stub\Stub;
use SebastianBergmann\Exporter\Exporter;

/**
* Holds matcher and value of mocked method
*/
class ConsecutiveMap
class ConsecutiveMap implements Stub
{
private array $consecutiveMap = [];

/**
* @var mixed
*/
private $value;

public function __construct(array $consecutiveMap)
{
$this->consecutiveMap = $consecutiveMap;
}

public function getMap(): array
public function invoke(Invocation $invocation)
{
$this->value = array_shift($this->consecutiveMap);

if ($this->value instanceof Stub) {
$this->value = $this->value->invoke($invocation);
}

return $this->value;
}

public function toString(): string
{
return $this->consecutiveMap;
$exporter = new Exporter;

return sprintf(
'return user-specified value %s',
$exporter->export($this->value),
);
}
}
54 changes: 50 additions & 4 deletions src/Stub/Expected.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use Closure;
use PHPUnit\Framework\MockObject\Rule\InvokedAtLeastOnce;
use PHPUnit\Framework\MockObject\Rule\InvokedCount;
use PHPUnit\Framework\MockObject\Stub\ReturnCallback;
use PHPUnit\Framework\MockObject\Stub\Stub;

class Expected
{
Expand All @@ -33,7 +35,7 @@ public static function never($params = null): StubMarshaler
{
return new StubMarshaler(
new InvokedCount(0),
self::closureIfNull($params)
self::stubIfClosure($params)
);
}

Expand Down Expand Up @@ -65,13 +67,20 @@ public static function never($params = null): StubMarshaler
* Expected::once(function() { return Faker::name(); });
* ```
*
* PHPUnit Stub can also be passed as parameter:
*
* ```php
* <?php
* Expected::exactly(3, new ReturnSelf());
* ```
*
* @param mixed $params
*/
public static function once($params = null): StubMarshaler
{
return new StubMarshaler(
new InvokedCount(1),
self::closureIfNull($params)
self::stubIfClosure($params)
);
}

Expand Down Expand Up @@ -103,14 +112,28 @@ public static function once($params = null): StubMarshaler
* <?php
* Expected::atLeastOnce(function() { return Faker::name(); });
* ```
*
* ConsecutiveMap can also be passed as parameter:
*
* ```php
* <?php
* Expected::exactly(3, Stub::consecutive(1,2,3));
* ```
*
* PHPUnit Stub can also be passed as parameter:
*
* ```php
* <?php
* Expected::exactly(3, new ReturnSelf());
* ```
*
* @param mixed $params
*/
public static function atLeastOnce($params = null): StubMarshaler
{
return new StubMarshaler(
new InvokedAtLeastOnce(),
self::closureIfNull($params)
self::stubIfClosure($params)
);
}

Expand Down Expand Up @@ -145,17 +168,40 @@ public static function atLeastOnce($params = null): StubMarshaler
* <?php
* Expected::exactly(function() { return Faker::name() });
* ```
*
* ConsecutiveMap can also be passed as parameter:
*
* ```php
* <?php
* Expected::exactly(3, Stub::consecutive(1,2,3));
* ```
*
* PHPUnit Stub can also be passed as parameter:
*
* ```php
* <?php
* Expected::exactly(3, new ReturnSelf());
* ```
*
* @param mixed $params
*/
public static function exactly(int $count, $params = null): StubMarshaler
{
return new StubMarshaler(
new InvokedCount($count),
self::closureIfNull($params)
self::stubIfClosure($params)
);
}

private static function stubIfClosure($params) : Stub
{
if ($params instanceof Stub) {
return $params;
}

return new ReturnCallback(self::closureIfNull($params));
}

private static function closureIfNull($params): Closure
{
if ($params instanceof Closure) {
Expand Down
4 changes: 4 additions & 0 deletions src/Stub/StubMarshaler.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Codeception\Stub;

use PHPUnit\Framework\MockObject\Rule\InvocationOrder;
use PHPUnit\Framework\MockObject\Stub\Stub;

/**
* Holds matcher and value of mocked method
Expand All @@ -26,6 +27,9 @@ public function getMatcher(): InvocationOrder
return $this->methodMatcher;
}

/**
* @return Stub
*/
public function getValue()
{
return $this->methodValue;
Expand Down
23 changes: 23 additions & 0 deletions tests/StubTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use Codeception\Stub;
use Codeception\Stub\StubMarshaler;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\MockObject\Stub\ConsecutiveCalls;
use PHPUnit\Framework\MockObject\Stub\ReturnCallback;
use PHPUnit\Framework\TestCase;

final class StubTest extends TestCase
Expand Down Expand Up @@ -274,6 +276,9 @@ public static function matcherProvider(): array
[1, Stub\Expected::exactly(1, fn() => null), null],
[1, Stub\Expected::exactly(1, fn(): string => 'hello world!'), 'hello world!'],
[1, Stub\Expected::exactly(1, 'hello world!'), 'hello world!'],
[1, Stub\Expected::exactly(1, Stub::consecutive('hello world!')), 'hello world!'],
[1, Stub\Expected::exactly(1, Stub::consecutive(new ReturnCallback(fn() => 'hello world!'))), 'hello world!'],
[1, Stub\Expected::exactly(1, new ReturnCallback(fn() => 'hello world!')), 'hello world!'],
];
}

Expand Down Expand Up @@ -366,6 +371,24 @@ public function testConsecutive()
$this->assertNull($dummy->helloWorld());
}

public function testConsecutiveWithAnonymousMethods()
{
$dummy = Stub::make('DummyClass', ['helloWorld' => new ConsecutiveCalls([
new ReturnCallback(fn() => 'david'),
new ReturnCallback(fn() => 'emma'),
new ReturnCallback(fn() => 'sam'),
new ReturnCallback(fn() => 'amy')
])]);

$this->assertEquals('david', $dummy->helloWorld());
$this->assertEquals('emma', $dummy->helloWorld());
$this->assertEquals('sam', $dummy->helloWorld());
$this->assertEquals('amy', $dummy->helloWorld());

// Expected null value when no more values
$this->assertNull($dummy->helloWorld());
}

public function testStubPrivateProperties()
{
$tester = Stub::construct(
Expand Down

0 comments on commit 5ff459b

Please sign in to comment.