From c81dce0f6d83b125b587756475558d44eca976f8 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Thu, 4 Jun 2020 01:33:33 +0200 Subject: [PATCH 1/2] feat(pending-higher-order-tests): adds code and tests --- composer.json | 3 ++ phpstan.neon | 1 + src/Console/Command.php | 3 +- src/Repositories/TestRepository.php | 12 +++++--- ...OrderMessages.php => HigherOrderTests.php} | 0 tests/Features/PendingHigherOrderTests.php | 29 +++++++++++++++++++ 6 files changed, 43 insertions(+), 5 deletions(-) rename tests/Features/{HigherOrderMessages.php => HigherOrderTests.php} (100%) create mode 100644 tests/Features/PendingHigherOrderTests.php diff --git a/composer.json b/composer.json index 2f2ffd62..f832fda5 100644 --- a/composer.json +++ b/composer.json @@ -78,6 +78,9 @@ ] }, "extra": { + "branch-alias": { + "dev-master": "0.2.x-dev" + }, "laravel": { "providers": [ "Pest\\Laravel\\PestServiceProvider" diff --git a/phpstan.neon b/phpstan.neon index 759697e0..866178f7 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -20,3 +20,4 @@ parameters: - "# with null as default value#" - "#Using \\$this in static method#" - "#has parameter \\$closure with default value.#" + - "#has parameter \\$description with default value.#" diff --git a/src/Console/Command.php b/src/Console/Command.php index b4dc3c75..290dbe04 100644 --- a/src/Console/Command.php +++ b/src/Console/Command.php @@ -88,6 +88,8 @@ final class Command extends BaseCommand */ $this->arguments = AddsDefaults::to($this->arguments); + LoadStructure::in($this->testSuite->rootPath); + $testRunner = new TestRunner($this->arguments['loader']); $testSuite = $this->arguments['test']; @@ -109,7 +111,6 @@ final class Command extends BaseCommand $this->arguments['test'] = $testSuite; } - LoadStructure::in($this->testSuite->rootPath); AddsTests::to($testSuite, $this->testSuite); return $testRunner; diff --git a/src/Repositories/TestRepository.php b/src/Repositories/TestRepository.php index 2aefe320..3f4cb99e 100644 --- a/src/Repositories/TestRepository.php +++ b/src/Repositories/TestRepository.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Pest\Repositories; +use Pest\Exceptions\ShouldNotHappen; use Pest\Exceptions\TestAlreadyExist; use Pest\Exceptions\TestCaseAlreadyInUse; use Pest\Exceptions\TestCaseClassOrTraitNotFound; @@ -54,7 +55,6 @@ final class TestRepository if ($testCase->class !== \PHPUnit\Framework\TestCase::class) { throw new TestCaseAlreadyInUse($testCase->class, $class, $filename); } - $testCase->class = $class; } elseif (trait_exists($class)) { $testCase->traits[] = $class; @@ -62,9 +62,9 @@ final class TestRepository } $testCase - ->factoryProxies - // Consider set the real line here. - ->add($filename, 0, 'addGroups', [$groups]); + ->factoryProxies + // Consider set the real line here. + ->add($filename, 0, 'addGroups', [$groups]); } }; @@ -113,6 +113,10 @@ final class TestRepository */ public function set(TestCaseFactory $test): void { + if ($test->description === null) { + throw ShouldNotHappen::fromMessage('Trying to create a test without description.'); + } + if (array_key_exists(sprintf('%s@%s', $test->filename, $test->description), $this->state)) { throw new TestAlreadyExist($test->filename, $test->description); } diff --git a/tests/Features/HigherOrderMessages.php b/tests/Features/HigherOrderTests.php similarity index 100% rename from tests/Features/HigherOrderMessages.php rename to tests/Features/HigherOrderTests.php diff --git a/tests/Features/PendingHigherOrderTests.php b/tests/Features/PendingHigherOrderTests.php new file mode 100644 index 00000000..de178c51 --- /dev/null +++ b/tests/Features/PendingHigherOrderTests.php @@ -0,0 +1,29 @@ +get($route); +} + +trait Gettable +{ + /** + * @return TestCase|Gettable + */ + public function get(string $route) + { + assertNotEmpty($route); + + return $this; + } +} + +get('foo')->get('bar')->assertTrue(true); +get('foo')->assertTrue(true); From aa1917c28d9b69c2bd1d51f986c4f61318ee7e16 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Thu, 4 Jun 2020 01:34:03 +0200 Subject: [PATCH 2/2] 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