From aa1917c28d9b69c2bd1d51f986c4f61318ee7e16 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Thu, 4 Jun 2020 01:34:03 +0200 Subject: [PATCH] feat(pending-higher-order-tests): adds code and tests --- src/Factories/TestCaseFactory.php | 14 +++++-- src/PendingObjects/TestCall.php | 62 +++++++++++++++++++++++------- src/Support/Backtrace.php | 33 +++++++++++++++- src/Support/HigherOrderMessage.php | 4 +- src/globals.php | 6 +-- tests/.snapshots/success.txt | 10 +++-- 6 files changed, 103 insertions(+), 26 deletions(-) diff --git a/src/Factories/TestCaseFactory.php b/src/Factories/TestCaseFactory.php index b04efb6c..e36613ab 100644 --- a/src/Factories/TestCaseFactory.php +++ b/src/Factories/TestCaseFactory.php @@ -8,6 +8,7 @@ use Closure; use Pest\Concerns; use Pest\Contracts\HasPrintableTestCaseName; use Pest\Datasets; +use Pest\Exceptions\ShouldNotHappen; use Pest\Support\HigherOrderMessageCollection; use Pest\Support\NullClosure; use Pest\TestSuite; @@ -39,9 +40,10 @@ final class TestCaseFactory /** * Holds the test description. * - * @readonly + * If the description is null, means that it + * will be created with the given assertions. * - * @var string + * @var string|null */ public $description; @@ -104,7 +106,7 @@ final class TestCaseFactory /** * Creates a new anonymous test case pending object. */ - public function __construct(string $filename, string $description, Closure $closure = null) + public function __construct(string $filename, string $description = null, Closure $closure = null) { $this->filename = $filename; $this->description = $description; @@ -122,6 +124,10 @@ final class TestCaseFactory */ public function build(TestSuite $testSuite): array { + if ($this->description === null) { + throw ShouldNotHappen::fromMessage('Description can not be empty.'); + } + $chains = $this->chains; $proxies = $this->proxies; $factoryTest = $this->test; @@ -160,7 +166,7 @@ final class TestCaseFactory // Limit to A-Z, a-z, 0-9, '_', '-'. $relativePath = (string) preg_replace('/[^A-Za-z0-9.\/]/', '', $relativePath); - $classFQN = 'P\\' . basename(ucfirst(str_replace(DIRECTORY_SEPARATOR, '\\', $relativePath)), '.php'); + $classFQN = 'P\\' . basename(ucfirst(str_replace(DIRECTORY_SEPARATOR, '\\', $relativePath)), '.php'); if (class_exists($classFQN)) { return $classFQN; diff --git a/src/PendingObjects/TestCall.php b/src/PendingObjects/TestCall.php index b25ec537..7b7549ae 100644 --- a/src/PendingObjects/TestCall.php +++ b/src/PendingObjects/TestCall.php @@ -9,12 +9,22 @@ use Pest\Factories\TestCaseFactory; use Pest\Support\Backtrace; use Pest\Support\NullClosure; use Pest\TestSuite; +use SebastianBergmann\Exporter\Exporter; /** * @internal */ final class TestCall { + /** + * Holds the test suite. + * + * @readonly + * + * @var TestSuite + */ + private $testSuite; + /** * Holds the test case factory. * @@ -24,14 +34,23 @@ final class TestCall */ private $testCaseFactory; + /** + * If test call is descriptionLess. + * + * @readonly + * + * @var bool + */ + private $descriptionLess = false; + /** * Creates a new instance of a pending test call. */ - public function __construct(TestSuite $testSuite, string $filename, string $description, Closure $closure = null) + public function __construct(TestSuite $testSuite, string $filename, string $description = null, Closure $closure = null) { $this->testCaseFactory = new TestCaseFactory($filename, $description, $closure); - - $testSuite->tests->set($this->testCaseFactory); + $this->testSuite = $testSuite; + $this->descriptionLess = $description === null; } /** @@ -40,13 +59,13 @@ final class TestCall public function throws(string $exceptionClass, string $exceptionMessage = null): TestCall { $this->testCaseFactory - ->proxies - ->add(Backtrace::file(), Backtrace::line(), 'expectException', [$exceptionClass]); + ->proxies + ->add(Backtrace::file(), Backtrace::line(), 'expectException', [$exceptionClass]); if (is_string($exceptionMessage)) { $this->testCaseFactory - ->proxies - ->add(Backtrace::file(), Backtrace::line(), 'expectExceptionMessage', [$exceptionMessage]); + ->proxies + ->add(Backtrace::file(), Backtrace::line(), 'expectExceptionMessage', [$exceptionMessage]); } return $this; @@ -81,8 +100,8 @@ final class TestCall public function group(string ...$groups): TestCall { $this->testCaseFactory - ->factoryProxies - ->add(Backtrace::file(), Backtrace::line(), 'addGroups', [$groups]); + ->factoryProxies + ->add(Backtrace::file(), Backtrace::line(), 'addGroups', [$groups]); return $this; } @@ -110,8 +129,8 @@ final class TestCall if ($condition() !== false) { $this->testCaseFactory - ->chains - ->add(Backtrace::file(), Backtrace::line(), 'markTestSkipped', [$message]); + ->chains + ->add(Backtrace::file(), Backtrace::line(), 'markTestSkipped', [$message]); } return $this; @@ -125,9 +144,26 @@ final class TestCall public function __call(string $name, array $arguments): self { $this->testCaseFactory - ->chains - ->add(Backtrace::file(), Backtrace::line(), $name, $arguments); + ->chains + ->add(Backtrace::file(), Backtrace::line(), $name, $arguments); + + if ($this->descriptionLess) { + $exporter = new Exporter(); + if ($this->testCaseFactory->description !== null) { + $this->testCaseFactory->description .= ' → '; + } + $this->testCaseFactory->description .= sprintf('%s %s', $name, $exporter->shortenedRecursiveExport($arguments)); + } return $this; } + + /** + * Adds the current test case factory + * to the tests repository. + */ + public function __destruct() + { + $this->testSuite->tests->set($this->testCaseFactory); + } } diff --git a/src/Support/Backtrace.php b/src/Support/Backtrace.php index e2199896..0347932f 100644 --- a/src/Support/Backtrace.php +++ b/src/Support/Backtrace.php @@ -4,17 +4,46 @@ declare(strict_types=1); namespace Pest\Support; +use Pest\Exceptions\ShouldNotHappen; + /** * @internal */ final class Backtrace { + /** + * @var string + */ + private const FILE = 'file'; + + /** + * Returns the current test file. + */ + public static function testFile(): string + { + $current = null; + + foreach (debug_backtrace() as $trace) { + if (Str::endsWith($trace[self::FILE], 'vendor/phpunit/phpunit/src/Util/FileLoader.php')) { + break; + } + + $current = $trace; + } + + if ($current === null) { + throw ShouldNotHappen::fromMessage('Test file not found.'); + } + + return $current[self::FILE]; + } + /** * Returns the filename that called the current function/method. */ public static function file(): string { - return debug_backtrace()[1]['file']; + return debug_backtrace()[1][self::FILE]; } /** @@ -22,7 +51,7 @@ final class Backtrace */ public static function dirname(): string { - return dirname(debug_backtrace()[1]['file']); + return dirname(debug_backtrace()[1][self::FILE]); } /** diff --git a/src/Support/HigherOrderMessage.php b/src/Support/HigherOrderMessage.php index d5bb0385..e5e4e21b 100644 --- a/src/Support/HigherOrderMessage.php +++ b/src/Support/HigherOrderMessage.php @@ -78,7 +78,9 @@ final class HigherOrderMessage if ($throwable->getMessage() === sprintf(self::UNDEFINED_METHOD, $this->methodName)) { /** @var \ReflectionClass $reflection */ - $reflection = (new ReflectionClass($target))->getParentClass(); + $reflection = new ReflectionClass($target); + /* @phpstan-ignore-next-line */ + $reflection = $reflection->getParentClass() ?: $reflection; Reflection::setPropertyValue($throwable, 'message', sprintf('Call to undefined method %s::%s()', $reflection->getName(), $this->methodName)); } diff --git a/src/globals.php b/src/globals.php index fa71e55c..a15f6bea 100644 --- a/src/globals.php +++ b/src/globals.php @@ -66,7 +66,7 @@ function test(string $description = null, Closure $closure = null) return new HigherOrderTapProxy(TestSuite::getInstance()->test); } - $filename = Backtrace::file(); + $filename = Backtrace::testFile(); return new TestCall(TestSuite::getInstance(), $filename, $description, $closure); } @@ -80,9 +80,9 @@ function test(string $description = null, Closure $closure = null) */ function it(string $description, Closure $closure = null): TestCall { - $filename = Backtrace::file(); + $description = sprintf('it %s', $description); - return new TestCall(TestSuite::getInstance(), $filename, sprintf('it %s', $description), $closure); + return test($description, $closure); } /** diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index 4509aee3..e34ef134 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -53,7 +53,7 @@ ✓ it allows to call underlying protected/private methods ✓ it throws error if method do not exist - PASS Tests\Features\HigherOrderMessages + PASS Tests\Features\HigherOrderTests ✓ it proxies calls to object PASS Tests\Features\It @@ -63,6 +63,10 @@ PASS Tests\Features\Mocks ✓ it has bar + PASS Tests\Features\PendingHigherOrderTests + ✓ get 'foo' → get 'bar' → assert true true + ✓ get 'foo' → assert true true + WARN Tests\Features\Skip ✓ it do not skips s it skips with truthy @@ -130,5 +134,5 @@ WARN Tests\Visual\Success s visual snapshot of test suite on success - Tests: 6 skipped, 69 passed - Time: 2.34s + Tests: 6 skipped, 71 passed + Time: 2.89s