diff --git a/src/Stub.php b/src/Stub.php index d57db23..19e7ff3 100644 --- a/src/Stub.php +++ b/src/Stub.php @@ -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; @@ -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) @@ -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) @@ -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 { diff --git a/src/Stub/ConsecutiveMap.php b/src/Stub/ConsecutiveMap.php index b0df2ab..9117519 100644 --- a/src/Stub/ConsecutiveMap.php +++ b/src/Stub/ConsecutiveMap.php @@ -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), + ); } } diff --git a/src/Stub/Expected.php b/src/Stub/Expected.php index 7854505..5e1c2ab 100644 --- a/src/Stub/Expected.php +++ b/src/Stub/Expected.php @@ -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 { @@ -33,7 +35,7 @@ public static function never($params = null): StubMarshaler { return new StubMarshaler( new InvokedCount(0), - self::closureIfNull($params) + self::stubIfClosure($params) ); } @@ -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 + * methodMatcher; } + /** + * @return Stub + */ public function getValue() { return $this->methodValue; diff --git a/tests/StubTest.php b/tests/StubTest.php index e56c3b3..c05e104 100644 --- a/tests/StubTest.php +++ b/tests/StubTest.php @@ -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 @@ -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!'], ]; } @@ -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(