mirror of
https://github.com/pestphp/pest.git
synced 2026-03-06 07:47:22 +01:00
feat(pending-higher-order-tests): adds code and tests
This commit is contained in:
@ -8,6 +8,7 @@ use Closure;
|
|||||||
use Pest\Concerns;
|
use Pest\Concerns;
|
||||||
use Pest\Contracts\HasPrintableTestCaseName;
|
use Pest\Contracts\HasPrintableTestCaseName;
|
||||||
use Pest\Datasets;
|
use Pest\Datasets;
|
||||||
|
use Pest\Exceptions\ShouldNotHappen;
|
||||||
use Pest\Support\HigherOrderMessageCollection;
|
use Pest\Support\HigherOrderMessageCollection;
|
||||||
use Pest\Support\NullClosure;
|
use Pest\Support\NullClosure;
|
||||||
use Pest\TestSuite;
|
use Pest\TestSuite;
|
||||||
@ -39,9 +40,10 @@ final class TestCaseFactory
|
|||||||
/**
|
/**
|
||||||
* Holds the test description.
|
* 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;
|
public $description;
|
||||||
|
|
||||||
@ -104,7 +106,7 @@ final class TestCaseFactory
|
|||||||
/**
|
/**
|
||||||
* Creates a new anonymous test case pending object.
|
* 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->filename = $filename;
|
||||||
$this->description = $description;
|
$this->description = $description;
|
||||||
@ -122,6 +124,10 @@ final class TestCaseFactory
|
|||||||
*/
|
*/
|
||||||
public function build(TestSuite $testSuite): array
|
public function build(TestSuite $testSuite): array
|
||||||
{
|
{
|
||||||
|
if ($this->description === null) {
|
||||||
|
throw ShouldNotHappen::fromMessage('Description can not be empty.');
|
||||||
|
}
|
||||||
|
|
||||||
$chains = $this->chains;
|
$chains = $this->chains;
|
||||||
$proxies = $this->proxies;
|
$proxies = $this->proxies;
|
||||||
$factoryTest = $this->test;
|
$factoryTest = $this->test;
|
||||||
@ -160,7 +166,7 @@ final class TestCaseFactory
|
|||||||
// Limit to A-Z, a-z, 0-9, '_', '-'.
|
// Limit to A-Z, a-z, 0-9, '_', '-'.
|
||||||
$relativePath = (string) preg_replace('/[^A-Za-z0-9.\/]/', '', $relativePath);
|
$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)) {
|
if (class_exists($classFQN)) {
|
||||||
return $classFQN;
|
return $classFQN;
|
||||||
|
|||||||
@ -9,12 +9,22 @@ use Pest\Factories\TestCaseFactory;
|
|||||||
use Pest\Support\Backtrace;
|
use Pest\Support\Backtrace;
|
||||||
use Pest\Support\NullClosure;
|
use Pest\Support\NullClosure;
|
||||||
use Pest\TestSuite;
|
use Pest\TestSuite;
|
||||||
|
use SebastianBergmann\Exporter\Exporter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
final class TestCall
|
final class TestCall
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Holds the test suite.
|
||||||
|
*
|
||||||
|
* @readonly
|
||||||
|
*
|
||||||
|
* @var TestSuite
|
||||||
|
*/
|
||||||
|
private $testSuite;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds the test case factory.
|
* Holds the test case factory.
|
||||||
*
|
*
|
||||||
@ -24,14 +34,23 @@ final class TestCall
|
|||||||
*/
|
*/
|
||||||
private $testCaseFactory;
|
private $testCaseFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If test call is descriptionLess.
|
||||||
|
*
|
||||||
|
* @readonly
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $descriptionLess = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of a pending test call.
|
* 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);
|
$this->testCaseFactory = new TestCaseFactory($filename, $description, $closure);
|
||||||
|
$this->testSuite = $testSuite;
|
||||||
$testSuite->tests->set($this->testCaseFactory);
|
$this->descriptionLess = $description === null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -40,13 +59,13 @@ final class TestCall
|
|||||||
public function throws(string $exceptionClass, string $exceptionMessage = null): TestCall
|
public function throws(string $exceptionClass, string $exceptionMessage = null): TestCall
|
||||||
{
|
{
|
||||||
$this->testCaseFactory
|
$this->testCaseFactory
|
||||||
->proxies
|
->proxies
|
||||||
->add(Backtrace::file(), Backtrace::line(), 'expectException', [$exceptionClass]);
|
->add(Backtrace::file(), Backtrace::line(), 'expectException', [$exceptionClass]);
|
||||||
|
|
||||||
if (is_string($exceptionMessage)) {
|
if (is_string($exceptionMessage)) {
|
||||||
$this->testCaseFactory
|
$this->testCaseFactory
|
||||||
->proxies
|
->proxies
|
||||||
->add(Backtrace::file(), Backtrace::line(), 'expectExceptionMessage', [$exceptionMessage]);
|
->add(Backtrace::file(), Backtrace::line(), 'expectExceptionMessage', [$exceptionMessage]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
@ -81,8 +100,8 @@ final class TestCall
|
|||||||
public function group(string ...$groups): TestCall
|
public function group(string ...$groups): TestCall
|
||||||
{
|
{
|
||||||
$this->testCaseFactory
|
$this->testCaseFactory
|
||||||
->factoryProxies
|
->factoryProxies
|
||||||
->add(Backtrace::file(), Backtrace::line(), 'addGroups', [$groups]);
|
->add(Backtrace::file(), Backtrace::line(), 'addGroups', [$groups]);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -110,8 +129,8 @@ final class TestCall
|
|||||||
|
|
||||||
if ($condition() !== false) {
|
if ($condition() !== false) {
|
||||||
$this->testCaseFactory
|
$this->testCaseFactory
|
||||||
->chains
|
->chains
|
||||||
->add(Backtrace::file(), Backtrace::line(), 'markTestSkipped', [$message]);
|
->add(Backtrace::file(), Backtrace::line(), 'markTestSkipped', [$message]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
@ -125,9 +144,26 @@ final class TestCall
|
|||||||
public function __call(string $name, array $arguments): self
|
public function __call(string $name, array $arguments): self
|
||||||
{
|
{
|
||||||
$this->testCaseFactory
|
$this->testCaseFactory
|
||||||
->chains
|
->chains
|
||||||
->add(Backtrace::file(), Backtrace::line(), $name, $arguments);
|
->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;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the current test case factory
|
||||||
|
* to the tests repository.
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
$this->testSuite->tests->set($this->testCaseFactory);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,17 +4,46 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Pest\Support;
|
namespace Pest\Support;
|
||||||
|
|
||||||
|
use Pest\Exceptions\ShouldNotHappen;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
final class Backtrace
|
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.
|
* Returns the filename that called the current function/method.
|
||||||
*/
|
*/
|
||||||
public static function file(): string
|
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
|
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)) {
|
if ($throwable->getMessage() === sprintf(self::UNDEFINED_METHOD, $this->methodName)) {
|
||||||
/** @var \ReflectionClass $reflection */
|
/** @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));
|
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);
|
return new HigherOrderTapProxy(TestSuite::getInstance()->test);
|
||||||
}
|
}
|
||||||
|
|
||||||
$filename = Backtrace::file();
|
$filename = Backtrace::testFile();
|
||||||
|
|
||||||
return new TestCall(TestSuite::getInstance(), $filename, $description, $closure);
|
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
|
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 allows to call underlying protected/private methods
|
||||||
✓ it throws error if method do not exist
|
✓ it throws error if method do not exist
|
||||||
|
|
||||||
PASS Tests\Features\HigherOrderMessages
|
PASS Tests\Features\HigherOrderTests
|
||||||
✓ it proxies calls to object
|
✓ it proxies calls to object
|
||||||
|
|
||||||
PASS Tests\Features\It
|
PASS Tests\Features\It
|
||||||
@ -63,6 +63,10 @@
|
|||||||
PASS Tests\Features\Mocks
|
PASS Tests\Features\Mocks
|
||||||
✓ it has bar
|
✓ it has bar
|
||||||
|
|
||||||
|
PASS Tests\Features\PendingHigherOrderTests
|
||||||
|
✓ get 'foo' → get 'bar' → assert true true
|
||||||
|
✓ get 'foo' → assert true true
|
||||||
|
|
||||||
WARN Tests\Features\Skip
|
WARN Tests\Features\Skip
|
||||||
✓ it do not skips
|
✓ it do not skips
|
||||||
s it skips with truthy
|
s it skips with truthy
|
||||||
@ -130,5 +134,5 @@
|
|||||||
WARN Tests\Visual\Success
|
WARN Tests\Visual\Success
|
||||||
s visual snapshot of test suite on success
|
s visual snapshot of test suite on success
|
||||||
|
|
||||||
Tests: 6 skipped, 69 passed
|
Tests: 6 skipped, 71 passed
|
||||||
Time: 2.34s
|
Time: 2.89s
|
||||||
|
|||||||
Reference in New Issue
Block a user