mirror of
https://github.com/pestphp/pest.git
synced 2026-03-06 15:57:21 +01:00
Merge pull request #66 from pestphp/feat/pending-higher-order-tests
feat: adds pending higher order tests
This commit is contained in:
@ -78,6 +78,9 @@
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "0.2.x-dev"
|
||||
},
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Pest\\Laravel\\PestServiceProvider"
|
||||
|
||||
@ -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.#"
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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
|
||||
|
||||
29
tests/Features/PendingHigherOrderTests.php
Normal file
29
tests/Features/PendingHigherOrderTests.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
uses(Gettable::class);
|
||||
|
||||
/**
|
||||
* @return TestCase|Gettable
|
||||
*/
|
||||
function get(string $route)
|
||||
{
|
||||
return test()->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);
|
||||
Reference in New Issue
Block a user