diff --git a/src/Support/HigherOrderCallables.php b/src/Support/HigherOrderCallables.php new file mode 100644 index 00000000..08231c76 --- /dev/null +++ b/src/Support/HigherOrderCallables.php @@ -0,0 +1,33 @@ +target = $target; + } + + /** + * @template TValue + * + * @param callable(): TValue $callable + * + * @return TValue|object + */ + public function tap(callable $callable) + { + return Reflection::bindCallable($callable) ?? $this->target; + } +} diff --git a/src/Support/HigherOrderMessage.php b/src/Support/HigherOrderMessage.php index 9f363b5a..5c5ddd07 100644 --- a/src/Support/HigherOrderMessage.php +++ b/src/Support/HigherOrderMessage.php @@ -70,6 +70,10 @@ final class HigherOrderMessage */ public function call(object $target) { + if (($value = $this->retrieveHigherOrderCallable($target)) !== null) { + return $value; + } + try { return Reflection::call($target, $this->methodName, $this->arguments); } catch (Throwable $throwable) { @@ -88,6 +92,21 @@ final class HigherOrderMessage } } + /** + * Attempts to call one of the available Higher Order callables if it exists. + * + * @return mixed|null + */ + private function retrieveHigherOrderCallable(object $target) + { + if (in_array($this->methodName, get_class_methods(HigherOrderCallables::class), true)) { + /* @phpstan-ignore-next-line */ + return (new HigherOrderCallables($target))->{$this->methodName}(...$this->arguments); + } + + return null; + } + private static function getUndefinedMethodMessage(object $target, string $methodName): string { if (\PHP_MAJOR_VERSION >= 8) { diff --git a/src/Support/Reflection.php b/src/Support/Reflection.php index c25709b9..fbe7ba40 100644 --- a/src/Support/Reflection.php +++ b/src/Support/Reflection.php @@ -41,15 +41,25 @@ final class Reflection } if (is_callable($method)) { - return Closure::fromCallable($method)->bindTo( - TestSuite::getInstance()->test - )(...$args); + return static::bindCallable($method, $args); } throw $exception; } } + /** + * Bind a callable to the TestCase and return the result. + * + * @param array $args + * + * @return mixed + */ + public static function bindCallable(callable $callable, array $args = []) + { + return Closure::fromCallable($callable)->bindTo(TestSuite::getInstance()->test)(...$args); + } + /** * Infers the file name from the given closure. */ diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index e7ecc1a0..36099b43 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -409,6 +409,8 @@ PASS Tests\Features\HigherOrderTests ✓ it proxies calls to object ✓ it is capable doing multiple assertions + ✓ it can tap into the test + ✓ it can use the returned instance from a tap WARN Tests\Features\Incompleted … incompleted @@ -578,5 +580,5 @@ ✓ it is a test ✓ it uses correct parent class - Tests: 4 incompleted, 7 skipped, 362 passed + Tests: 4 incompleted, 7 skipped, 364 passed \ No newline at end of file diff --git a/tests/Features/HigherOrderTests.php b/tests/Features/HigherOrderTests.php index d01a18e5..ea5b80e1 100644 --- a/tests/Features/HigherOrderTests.php +++ b/tests/Features/HigherOrderTests.php @@ -1,5 +1,7 @@ assertTrue(true); it('proxies calls to object')->assertTrue(true); @@ -8,7 +10,14 @@ it('is capable doing multiple assertions') ->assertTrue(true) ->assertFalse(false); -//it('can tap into the test') +it('can tap into the test') + ->expect('foo')->toBeString()->toBe('foo') + ->tap(function () { expect($this)->toBeInstanceOf(TestCase::class); }) + ->and('hello world')->toBeString(); +it('can use the returned instance from a tap') + ->expect('foo')->toBeString()->toBe('foo') + ->tap(function () { return expect($this); }) + ->toBeInstanceOf(TestCase::class); afterEach()->assertTrue(true);