diff --git a/src/Concerns/TestCase.php b/src/Concerns/TestCase.php index 7667ce86..5dbac402 100644 --- a/src/Concerns/TestCase.php +++ b/src/Concerns/TestCase.php @@ -92,6 +92,8 @@ trait TestCase */ protected function setUp(): void { + TestSuite::getInstance()->test = $this; + parent::setUp(); $beforeEach = TestSuite::getInstance()->beforeEach->get(self::$__filename); @@ -109,6 +111,8 @@ trait TestCase $this->__callClosure($afterEach, func_get_args()); parent::tearDown(); + + TestSuite::getInstance()->test = null; } /** diff --git a/src/Support/HigherOrderTapProxy.php b/src/Support/HigherOrderTapProxy.php new file mode 100644 index 00000000..9e9506d2 --- /dev/null +++ b/src/Support/HigherOrderTapProxy.php @@ -0,0 +1,83 @@ +target = $target; + } + + /** + * Dynamically sets properties on the target. + * + * @param mixed $value + */ + public function __set(string $property, $value): void + { + // @phpstan-ignore-next-line + $this->target->{$property} = $value; + } + + /** + * Dynamically pass properties gets to the target. + * + * @return mixed + */ + public function __get(string $property) + { + try { + // @phpstan-ignore-next-line + return $this->target->{$property}; + } catch (\Throwable $throwable) { + Reflection::setPropertyValue($throwable, 'file', Backtrace::file()); + Reflection::setPropertyValue($throwable, 'line', Backtrace::line()); + + if (Str::startsWith($message = $throwable->getMessage(), self::UNDEFINED_PROPERTY)) { + /** @var \ReflectionClass $reflection */ + $reflection = (new ReflectionClass($this->target))->getParentClass(); + Reflection::setPropertyValue($throwable, 'message', sprintf('Undefined property %s::$%s', $reflection->getName(), $property)); + } + + throw $throwable; + } + } + + /** + * Dynamically pass method calls to the target. + * + * @param array $arguments + * + * @return mixed + */ + public function __call(string $methodName, array $arguments) + { + $filename = Backtrace::file(); + $line = Backtrace::line(); + + return (new HigherOrderMessage($filename, $line, $methodName, $arguments)) + ->call($this->target); + } +} diff --git a/src/TestSuite.php b/src/TestSuite.php index f9af39b5..1bd52e04 100644 --- a/src/TestSuite.php +++ b/src/TestSuite.php @@ -16,6 +16,13 @@ use Pest\Repositories\TestRepository; */ final class TestSuite { + /** + * Holds the current test case. + * + * @var \PHPUnit\Framework\TestCase|null + */ + public $test; + /** * Holds the tests repository. * diff --git a/src/globals.php b/src/globals.php index 4a6aaac9..4d137635 100644 --- a/src/globals.php +++ b/src/globals.php @@ -8,6 +8,7 @@ use Pest\PendingObjects\BeforeEachCall; use Pest\PendingObjects\TestCall; use Pest\PendingObjects\UsesCall; use Pest\Support\Backtrace; +use Pest\Support\HigherOrderTapProxy; use Pest\TestSuite; use PHPUnit\Framework\TestCase; @@ -59,8 +60,12 @@ function uses(string ...$classAndTraits): UsesCall * * @return TestCall|TestCase|mixed */ -function test(string $description, Closure $closure = null): TestCall +function test(string $description = null, Closure $closure = null) { + if ($description === null && TestSuite::getInstance()->test) { + return new HigherOrderTapProxy(TestSuite::getInstance()->test); + } + $filename = Backtrace::file(); return new TestCall(TestSuite::getInstance(), $filename, $description, $closure); diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index 4b544dc7..9164e835 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -47,6 +47,12 @@ ✓ it catch exceptions ✓ it catch exceptions and messages + PASS Tests\Features\Helpers + ✓ it can set/get properties on $this + ✓ it throws error if property do not exist + ✓ it allows to call underlying protected/private methods + ✓ it throws error if method do not exist + PASS Tests\Features\HigherOrderMessages ✓ it proxies calls to object @@ -126,5 +132,5 @@ WARN Tests\Visual\Success s visual snapshot of test suite on success - Tests: 6 skipped, 65 passed - Time: 2.50s + Tests: 6 skipped, 69 passed + Time: 2.63s diff --git a/tests/Features/Helpers.php b/tests/Features/Helpers.php new file mode 100644 index 00000000..b32c8ab6 --- /dev/null +++ b/tests/Features/Helpers.php @@ -0,0 +1,43 @@ +user = 'nuno'; +} + +it('can set/get properties on $this', function () { + addName(); + assertEquals('nuno', $this->name); +}); + +it('throws error if property do not exist', function () { + test()->user; +})->throws(\Whoops\Exception\ErrorException::class, 'Undefined property PHPUnit\Framework\TestCase::$name'); + +class User +{ + public function getName() + { + return 'nuno'; + } +} + +function mockUser() +{ + $mock = test()->createMock(User::class); + + $mock->method('getName') + ->willReturn('maduro'); + + return $mock; +} + +it('allows to call underlying protected/private methods', function () { + $user = mockUser(); + + assertEquals('maduro', $user->getName()); +}); + +it('throws error if method do not exist', function () { + test()->name(); +})->throws(\ReflectionException::class, 'Call to undefined method PHPUnit\Framework\TestCase::name()');