mirror of
https://github.com/pestphp/pest.git
synced 2026-03-06 15:57:21 +01:00
Merge branch 'master' into performs_no_expectations
# Conflicts: # src/PendingCalls/TestCall.php
This commit is contained in:
8
.github/workflows/static.yml
vendored
8
.github/workflows/static.yml
vendored
@ -20,10 +20,10 @@ jobs:
|
||||
coverage: none
|
||||
|
||||
- name: Install Dependencies
|
||||
run: composer update --no-interaction --no-progress
|
||||
run: composer update --no-interaction --no-progress --ansi
|
||||
|
||||
- name: Run PHP-CS-Fixer
|
||||
run: PHP_CS_FIXER_IGNORE_ENV=true vendor/bin/php-cs-fixer fix -v --allow-risky=yes --dry-run
|
||||
run: PHP_CS_FIXER_IGNORE_ENV=true vendor/bin/php-cs-fixer fix -v --allow-risky=yes --dry-run --ansi
|
||||
|
||||
phpstan:
|
||||
runs-on: ubuntu-latest
|
||||
@ -45,7 +45,7 @@ jobs:
|
||||
coverage: none
|
||||
|
||||
- name: Install Dependencies
|
||||
run: composer update --prefer-stable --no-interaction --no-progress
|
||||
run: composer update --prefer-stable --no-interaction --no-progress --ansi
|
||||
|
||||
- name: Run PHPStan
|
||||
run: vendor/bin/phpstan analyse --no-progress --debug
|
||||
run: vendor/bin/phpstan analyse --no-progress --debug --ansi
|
||||
|
||||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@ -36,7 +36,7 @@ jobs:
|
||||
echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
|
||||
|
||||
- name: Install PHP dependencies
|
||||
run: composer update --${{ matrix.dependency-version }} --no-interaction --no-progress
|
||||
run: composer update --${{ matrix.dependency-version }} --no-interaction --no-progress --ansi
|
||||
|
||||
- name: Unit Tests
|
||||
run: php bin/pest --colors=always --exclude-group=integration
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
<a href="https://supportukrainenow.org/"><img src="https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct.svg" width="100%"></a>
|
||||
|
||||
------
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/pestphp/art/master/readme.png" width="600" alt="PEST">
|
||||
<p align="center">
|
||||
@ -21,6 +25,7 @@ We would like to extend our thanks to the following sponsors for funding Pest de
|
||||
|
||||
### Platinum Sponsors
|
||||
|
||||
- **[Advent](https://advent.dev)**
|
||||
- **[Spatie](https://spatie.be)**
|
||||
- **[Worksome](https://www.worksome.com/)**
|
||||
|
||||
|
||||
@ -48,6 +48,7 @@ final class BootFiles
|
||||
if (is_dir($filename)) {
|
||||
$directory = new RecursiveDirectoryIterator($filename);
|
||||
$iterator = new RecursiveIteratorIterator($directory);
|
||||
/** @var \DirectoryIterator $file */
|
||||
foreach ($iterator as $file) {
|
||||
$this->load($file->__toString());
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ trait Pipeable
|
||||
$this->pipe($name, function ($next, ...$arguments) use ($handler, $filter) {
|
||||
/* @phpstan-ignore-next-line */
|
||||
if ($filter($this->value, ...$arguments)) {
|
||||
//@phpstan-ignore-next-line
|
||||
// @phpstan-ignore-next-line
|
||||
$handler->bindTo($this, get_class($this))(...$arguments);
|
||||
|
||||
return;
|
||||
|
||||
@ -76,6 +76,22 @@ final class Expectation
|
||||
return $this->toBeJson()->and($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the expectation value.
|
||||
*
|
||||
* @return self<TValue>
|
||||
*/
|
||||
public function dump(mixed ...$arguments): self
|
||||
{
|
||||
if (function_exists('dump')) {
|
||||
dump($this->value, ...$arguments);
|
||||
} else {
|
||||
var_dump($this->value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the expectation value and end the script.
|
||||
*
|
||||
@ -151,7 +167,6 @@ final class Expectation
|
||||
throw new BadMethodCallException('Expectation value is not iterable.');
|
||||
}
|
||||
|
||||
//@phpstan-ignore-next-line
|
||||
$value = is_array($this->value) ? $this->value : iterator_to_array($this->value);
|
||||
$keys = array_keys($value);
|
||||
$values = array_values($value);
|
||||
@ -292,7 +307,7 @@ final class Expectation
|
||||
private function getExpectationClosure(string $name): Closure
|
||||
{
|
||||
if (method_exists(Mixins\Expectation::class, $name)) {
|
||||
//@phpstan-ignore-next-line
|
||||
// @phpstan-ignore-next-line
|
||||
return Closure::fromCallable([new Mixins\Expectation($this->value), $name]);
|
||||
}
|
||||
|
||||
|
||||
30
src/Factories/Annotations/CoversNothing.php
Normal file
30
src/Factories/Annotations/CoversNothing.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Factories\Annotations;
|
||||
|
||||
use Pest\Factories\Covers\CoversNothing as CoversNothingFactory;
|
||||
use Pest\Factories\TestCaseMethodFactory;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class CoversNothing
|
||||
{
|
||||
/**
|
||||
* Adds annotations regarding the "depends" feature.
|
||||
*
|
||||
* @param array<int, string> $annotations
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function __invoke(TestCaseMethodFactory $method, array $annotations): array
|
||||
{
|
||||
if (($method->covers[0] ?? null) instanceof CoversNothingFactory) {
|
||||
$annotations[] = '@coversNothing';
|
||||
}
|
||||
|
||||
return $annotations;
|
||||
}
|
||||
}
|
||||
30
src/Factories/Attributes/Attribute.php
Normal file
30
src/Factories/Attributes/Attribute.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Factories\Attributes;
|
||||
|
||||
use Pest\Factories\TestCaseMethodFactory;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class Attribute
|
||||
{
|
||||
/**
|
||||
* Determine if the attribute should be placed above the class instead of above the method.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public const ABOVE_CLASS = false;
|
||||
|
||||
/**
|
||||
* @param array<int, string> $attributes
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function __invoke(TestCaseMethodFactory $method, array $attributes): array // @phpstan-ignore-line
|
||||
{
|
||||
return $attributes;
|
||||
}
|
||||
}
|
||||
47
src/Factories/Attributes/Covers.php
Normal file
47
src/Factories/Attributes/Covers.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Factories\Attributes;
|
||||
|
||||
use Pest\Factories\Covers\CoversClass;
|
||||
use Pest\Factories\Covers\CoversFunction;
|
||||
use Pest\Factories\TestCaseMethodFactory;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Covers extends Attribute
|
||||
{
|
||||
/**
|
||||
* Determine if the attribute should be placed above the classe instead of above the method.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public const ABOVE_CLASS = true;
|
||||
|
||||
/**
|
||||
* Adds attributes regarding the "covers" feature.
|
||||
*
|
||||
* @param array<int, string> $attributes
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function __invoke(TestCaseMethodFactory $method, array $attributes): array
|
||||
{
|
||||
foreach ($method->covers as $covering) {
|
||||
if ($covering instanceof CoversClass) {
|
||||
// Prepend a backslash for FQN classes
|
||||
if (str_contains($covering->class, '\\')) {
|
||||
$covering->class = '\\' . $covering->class;
|
||||
}
|
||||
|
||||
$attributes[] = "#[\PHPUnit\Framework\Attributes\CoversClass({$covering->class}::class)]";
|
||||
} elseif ($covering instanceof CoversFunction) {
|
||||
$attributes[] = "#[\PHPUnit\Framework\Attributes\CoversFunction('{$covering->function}')]";
|
||||
}
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
}
|
||||
15
src/Factories/Covers/CoversClass.php
Normal file
15
src/Factories/Covers/CoversClass.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Factories\Covers;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class CoversClass
|
||||
{
|
||||
public function __construct(public string $class)
|
||||
{
|
||||
}
|
||||
}
|
||||
15
src/Factories/Covers/CoversFunction.php
Normal file
15
src/Factories/Covers/CoversFunction.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Factories\Covers;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class CoversFunction
|
||||
{
|
||||
public function __construct(public string $function)
|
||||
{
|
||||
}
|
||||
}
|
||||
12
src/Factories/Covers/CoversNothing.php
Normal file
12
src/Factories/Covers/CoversNothing.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Factories\Covers;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class CoversNothing
|
||||
{
|
||||
}
|
||||
@ -33,6 +33,16 @@ final class TestCaseFactory
|
||||
private static array $annotations = [
|
||||
Annotations\Depends::class,
|
||||
Annotations\Groups::class,
|
||||
Annotations\CoversNothing::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* The list of attributes.
|
||||
*
|
||||
* @var array<int, class-string<\Pest\Factories\Attributes\Attribute>>
|
||||
*/
|
||||
private static array $attributes = [
|
||||
Attributes\Covers::class,
|
||||
];
|
||||
|
||||
/**
|
||||
@ -141,11 +151,33 @@ final class TestCaseFactory
|
||||
$classFQN .= $className;
|
||||
}
|
||||
|
||||
$classAvailableAttributes = array_filter(self::$attributes, fn (string $attribute) => $attribute::ABOVE_CLASS);
|
||||
$methodAvailableAttributes = array_filter(self::$attributes, fn (string $attribute) => !$attribute::ABOVE_CLASS);
|
||||
|
||||
$classAttributes = [];
|
||||
|
||||
foreach ($classAvailableAttributes as $attribute) {
|
||||
$classAttributes = array_reduce(
|
||||
$methods,
|
||||
fn (array $carry, TestCaseMethodFactory $methodFactory) => (new $attribute())->__invoke($methodFactory, $carry),
|
||||
$classAttributes
|
||||
);
|
||||
}
|
||||
|
||||
$methodsCode = implode('', array_map(
|
||||
fn (TestCaseMethodFactory $methodFactory) => $methodFactory->buildForEvaluation($classFQN, self::$annotations),
|
||||
fn (TestCaseMethodFactory $methodFactory) => $methodFactory->buildForEvaluation(
|
||||
$classFQN,
|
||||
self::$annotations,
|
||||
$methodAvailableAttributes
|
||||
),
|
||||
$methods
|
||||
));
|
||||
|
||||
$classAttributesCode = implode('', array_map(
|
||||
static fn (string $attribute) => sprintf("\n %s", $attribute),
|
||||
array_unique($classAttributes),
|
||||
));
|
||||
|
||||
try {
|
||||
eval("
|
||||
namespace $namespace;
|
||||
@ -153,6 +185,7 @@ final class TestCaseFactory
|
||||
use Pest\Repositories\DatasetsRepository as __PestDatasets;
|
||||
use Pest\TestSuite as __PestTestSuite;
|
||||
|
||||
$classAttributesCode
|
||||
final class $className extends $baseClass implements $hasPrintableTestCaseClassFQN {
|
||||
$traitsCode
|
||||
|
||||
|
||||
@ -47,6 +47,13 @@ final class TestCaseMethodFactory
|
||||
*/
|
||||
public array $groups = [];
|
||||
|
||||
/**
|
||||
* The covered classes and functions, if any.
|
||||
*
|
||||
* @var array<int, \Pest\Factories\Covers\CoversClass|\Pest\Factories\Covers\CoversFunction|\Pest\Factories\Covers\CoversNothing>
|
||||
*/
|
||||
public array $covers = [];
|
||||
|
||||
/**
|
||||
* Creates a new Factory instance.
|
||||
*/
|
||||
@ -106,9 +113,10 @@ final class TestCaseMethodFactory
|
||||
/**
|
||||
* Creates a PHPUnit method as a string ready for evaluation.
|
||||
*
|
||||
* @param array<int, class-string> $annotationsToUse
|
||||
* @param array<int, class-string> $annotationsToUse
|
||||
* @param array<int, class-string<\Pest\Factories\Attributes\Attribute>> $attributesToUse
|
||||
*/
|
||||
public function buildForEvaluation(string $classFQN, array $annotationsToUse): string
|
||||
public function buildForEvaluation(string $classFQN, array $annotationsToUse, array $attributesToUse): string
|
||||
{
|
||||
if ($this->description === null) {
|
||||
throw ShouldNotHappen::fromMessage('The test description may not be empty.');
|
||||
@ -122,12 +130,17 @@ final class TestCaseMethodFactory
|
||||
|
||||
$datasetsCode = '';
|
||||
$annotations = ['@test'];
|
||||
$attributes = [];
|
||||
|
||||
foreach ($annotationsToUse as $annotation) {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$annotations = (new $annotation())->__invoke($this, $annotations);
|
||||
}
|
||||
|
||||
foreach ($attributesToUse as $attribute) {
|
||||
$attributes = (new $attribute())->__invoke($this, $attributes);
|
||||
}
|
||||
|
||||
if (count($this->datasets) > 0) {
|
||||
$dataProviderName = $methodName . '_dataset';
|
||||
$annotations[] = "@dataProvider $dataProviderName";
|
||||
@ -138,10 +151,15 @@ final class TestCaseMethodFactory
|
||||
static fn ($annotation) => sprintf("\n * %s", $annotation), $annotations,
|
||||
));
|
||||
|
||||
$attributes = implode('', array_map(
|
||||
static fn ($attribute) => sprintf("\n %s", $attribute), $attributes,
|
||||
));
|
||||
|
||||
return <<<EOF
|
||||
|
||||
/**$annotations
|
||||
*/
|
||||
$attributes
|
||||
public function $methodName()
|
||||
{
|
||||
return \$this->__runTest(
|
||||
|
||||
@ -37,7 +37,7 @@ final class Kernel
|
||||
public static function boot(): self
|
||||
{
|
||||
foreach (self::$bootstrappers as $bootstrapper) {
|
||||
//@phpstan-ignore-next-line
|
||||
// @phpstan-ignore-next-line
|
||||
(new $bootstrapper())->__invoke();
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ namespace Pest\Mixins;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Closure;
|
||||
use Error;
|
||||
use InvalidArgumentException;
|
||||
use Pest\Exceptions\InvalidExpectationValue;
|
||||
use Pest\Support\Arr;
|
||||
@ -282,7 +283,7 @@ final class Expectation
|
||||
{
|
||||
$this->toBeObject();
|
||||
|
||||
//@phpstan-ignore-next-line
|
||||
// @phpstan-ignore-next-line
|
||||
Assert::assertTrue(property_exists($this->value, $name));
|
||||
|
||||
if (func_num_args() > 1) {
|
||||
@ -533,7 +534,7 @@ final class Expectation
|
||||
{
|
||||
Assert::assertIsString($this->value);
|
||||
|
||||
//@phpstan-ignore-next-line
|
||||
// @phpstan-ignore-next-line
|
||||
Assert::assertJson($this->value);
|
||||
|
||||
return $this;
|
||||
@ -579,7 +580,7 @@ final class Expectation
|
||||
try {
|
||||
Assert::assertTrue(Arr::has($array, $key));
|
||||
|
||||
/* @phpstan-ignore-next-line */
|
||||
/* @phpstan-ignore-next-line */
|
||||
} catch (ExpectationFailedException $exception) {
|
||||
throw new ExpectationFailedException("Failed asserting that an array has the key '$key'", $exception->getComparisonFailure());
|
||||
}
|
||||
@ -822,8 +823,12 @@ final class Expectation
|
||||
|
||||
try {
|
||||
($this->value)();
|
||||
} catch (Throwable $e) { // @phpstan-ignore-line
|
||||
} catch (Throwable $e) {
|
||||
if (!class_exists($exception)) {
|
||||
if ($e instanceof Error && $e->getMessage() === "Class \"$exception\" not found") {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
Assert::assertStringContainsString($exception, $e->getMessage());
|
||||
|
||||
return $this;
|
||||
|
||||
@ -5,6 +5,10 @@ declare(strict_types=1);
|
||||
namespace Pest\PendingCalls;
|
||||
|
||||
use Closure;
|
||||
use InvalidArgumentException;
|
||||
use Pest\Factories\Covers\CoversClass;
|
||||
use Pest\Factories\Covers\CoversFunction;
|
||||
use Pest\Factories\Covers\CoversNothing;
|
||||
use Pest\Factories\TestCaseMethodFactory;
|
||||
use Pest\Support\Backtrace;
|
||||
use Pest\Support\HigherOrderCallables;
|
||||
@ -45,9 +49,11 @@ final class TestCall
|
||||
/**
|
||||
* Asserts that the test throws the given `$exceptionClass` when called.
|
||||
*/
|
||||
public function throws(string $exception, string $exceptionMessage = null): TestCall
|
||||
public function throws(string|int $exception, string $exceptionMessage = null, int $exceptionCode = null): TestCall
|
||||
{
|
||||
if (class_exists($exception)) {
|
||||
if (is_int($exception)) {
|
||||
$exceptionCode = $exception;
|
||||
} elseif (class_exists($exception)) {
|
||||
$this->testCaseMethod
|
||||
->proxies
|
||||
->add(Backtrace::file(), Backtrace::line(), 'expectException', [$exception]);
|
||||
@ -61,6 +67,12 @@ final class TestCall
|
||||
->add(Backtrace::file(), Backtrace::line(), 'expectExceptionMessage', [$exceptionMessage]);
|
||||
}
|
||||
|
||||
if (is_int($exceptionCode)) {
|
||||
$this->testCaseMethod
|
||||
->proxies
|
||||
->add(Backtrace::file(), Backtrace::line(), 'expectExceptionCode', [$exceptionCode]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -69,7 +81,7 @@ final class TestCall
|
||||
*
|
||||
* @param (callable(): bool)|bool $condition
|
||||
*/
|
||||
public function throwsIf(callable|bool $condition, string $exception, string $exceptionMessage = null): TestCall
|
||||
public function throwsIf(callable|bool $condition, string|int $exception, string $exceptionMessage = null, int $exceptionCode = null): TestCall
|
||||
{
|
||||
$condition = is_callable($condition)
|
||||
? $condition
|
||||
@ -78,7 +90,7 @@ final class TestCall
|
||||
};
|
||||
|
||||
if ($condition()) {
|
||||
return $this->throws($exception, $exceptionMessage);
|
||||
return $this->throws($exception, $exceptionMessage, $exceptionCode);
|
||||
}
|
||||
|
||||
return $this;
|
||||
@ -160,6 +172,63 @@ final class TestCall
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the covered classes or methods.
|
||||
*/
|
||||
public function covers(string ...$classesOrFunctions): TestCall
|
||||
{
|
||||
foreach ($classesOrFunctions as $classOrFunction) {
|
||||
$isClass = class_exists($classOrFunction);
|
||||
$isMethod = function_exists($classOrFunction);
|
||||
|
||||
if (!$isClass && !$isMethod) {
|
||||
throw new InvalidArgumentException(sprintf('No class or method named "%s" has been found.', $classOrFunction));
|
||||
}
|
||||
|
||||
if ($isClass) {
|
||||
$this->coversClass($classOrFunction);
|
||||
} else {
|
||||
$this->coversFunction($classOrFunction);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the covered classes.
|
||||
*/
|
||||
public function coversClass(string ...$classes): TestCall
|
||||
{
|
||||
foreach ($classes as $class) {
|
||||
$this->testCaseMethod->covers[] = new CoversClass($class);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the covered functions.
|
||||
*/
|
||||
public function coversFunction(string ...$functions): TestCall
|
||||
{
|
||||
foreach ($functions as $function) {
|
||||
$this->testCaseMethod->covers[] = new CoversFunction($function);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets that the current test covers nothing.
|
||||
*/
|
||||
public function coversNothing(): TestCall
|
||||
{
|
||||
$this->testCaseMethod->covers = [new CoversNothing()];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Informs the test runner that no expectations happen in this test,
|
||||
* and its purpose is simply to check whether the given code can
|
||||
|
||||
@ -156,11 +156,11 @@ final class DatasetsRepository
|
||||
$datasets[$index] = iterator_to_array($datasets[$index]);
|
||||
}
|
||||
|
||||
//@phpstan-ignore-next-line
|
||||
// @phpstan-ignore-next-line
|
||||
foreach ($datasets[$index] as $key => $values) {
|
||||
$values = is_array($values) ? $values : [$values];
|
||||
$processedDataset[] = [
|
||||
'label' => self::getDatasetDescription($key, $values), //@phpstan-ignore-line
|
||||
'label' => self::getDatasetDescription($key, $values), // @phpstan-ignore-line
|
||||
'values' => $values,
|
||||
];
|
||||
}
|
||||
@ -189,7 +189,7 @@ final class DatasetsRepository
|
||||
$result = $tmp;
|
||||
}
|
||||
|
||||
//@phpstan-ignore-next-line
|
||||
// @phpstan-ignore-next-line
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
@ -85,7 +85,7 @@ final class Container
|
||||
}
|
||||
}
|
||||
|
||||
//@phpstan-ignore-next-line
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->get($candidate);
|
||||
},
|
||||
$constructor->getParameters()
|
||||
|
||||
@ -40,7 +40,7 @@ final class HigherOrderMessageCollection
|
||||
public function chain(object $target): void
|
||||
{
|
||||
foreach ($this->messages as $message) {
|
||||
//@phpstan-ignore-next-line
|
||||
// @phpstan-ignore-next-line
|
||||
$target = $message->call($target) ?? $target;
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
it('has plugin')->assertTrue(class_exists(CoveragePlugin::class));
|
||||
|
||||
it('adds coverage if --coverage exist', function () {
|
||||
$plugin = new CoveragePlugin(new ConsoleOutput());
|
||||
$plugin = new CoveragePlugin(new ConsoleOutput());
|
||||
$testSuite = TestSuite::getInstance();
|
||||
|
||||
expect($plugin->coverage)->toBeFalse();
|
||||
|
||||
78
tests/Features/Covers.php
Normal file
78
tests/Features/Covers.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
use Pest\Factories\Attributes\Covers;
|
||||
use Pest\PendingCalls\TestCall;
|
||||
use Pest\TestSuite;
|
||||
|
||||
$runCounter = 0;
|
||||
|
||||
class TestCoversClass1
|
||||
{
|
||||
}
|
||||
class TestCoversClass2
|
||||
{
|
||||
}
|
||||
|
||||
class TestCoversClass3
|
||||
{
|
||||
}
|
||||
|
||||
function testCoversFunction()
|
||||
{
|
||||
}
|
||||
|
||||
it('uses the correct PHPUnit attribute for class', function () {
|
||||
$attributes = (new ReflectionClass($this))->getAttributes();
|
||||
|
||||
expect($attributes[0]->getName())->toBe('PHPUnit\Framework\Attributes\CoversClass');
|
||||
expect($attributes[0]->getArguments()[0])->toBe('P\Tests\Features\TestCoversClass1');
|
||||
})->coversClass(TestCoversClass1::class);
|
||||
|
||||
it('uses the correct PHPUnit attribute for function', function () {
|
||||
$attributes = (new ReflectionClass($this))->getAttributes();
|
||||
|
||||
expect($attributes[1]->getName())->toBe('PHPUnit\Framework\Attributes\CoversFunction');
|
||||
expect($attributes[1]->getArguments()[0])->toBe('foo');
|
||||
})->coversFunction('foo');
|
||||
|
||||
it('removes duplicated attributes', function () {
|
||||
$attributes = (new ReflectionClass($this))->getAttributes();
|
||||
|
||||
expect($attributes[2]->getName())->toBe('PHPUnit\Framework\Attributes\CoversClass');
|
||||
expect($attributes[2]->getArguments()[0])->toBe('P\Tests\Features\TestCoversClass2');
|
||||
expect($attributes[3]->getName())->toBe('PHPUnit\Framework\Attributes\CoversClass');
|
||||
expect($attributes[3]->getArguments()[0])->toBe('Pest\Factories\Attributes\Covers');
|
||||
expect($attributes[4]->getName())->toBe('PHPUnit\Framework\Attributes\CoversFunction');
|
||||
expect($attributes[4]->getArguments()[0])->toBe('bar');
|
||||
expect($attributes[5]->getName())->toBe('PHPUnit\Framework\Attributes\CoversFunction');
|
||||
expect($attributes[5]->getArguments()[0])->toBe('baz');
|
||||
})
|
||||
->coversClass(TestCoversClass2::class, TestCoversClass1::class, Covers::class)
|
||||
->coversFunction('bar', 'foo', 'baz');
|
||||
|
||||
it('guesses if the given argument is a class or function', function () {
|
||||
$attributes = (new ReflectionClass($this))->getAttributes();
|
||||
|
||||
expect($attributes[6]->getName())->toBe('PHPUnit\Framework\Attributes\CoversClass');
|
||||
expect($attributes[6]->getArguments()[0])->toBe('P\Tests\Features\TestCoversClass3');
|
||||
expect($attributes[7]->getName())->toBe('PHPUnit\Framework\Attributes\CoversFunction');
|
||||
expect($attributes[7]->getArguments()[0])->toBe('testCoversFunction');
|
||||
})->covers(TestCoversClass3::class, 'testCoversFunction');
|
||||
|
||||
it('appends CoversNothing to method attributes', function () {
|
||||
$phpDoc = (new ReflectionClass($this))->getMethod($this->getName());
|
||||
|
||||
expect(str_contains($phpDoc->getDocComment(), '* @coversNothing'))->toBeTrue();
|
||||
})->coversNothing();
|
||||
|
||||
it('does not append CoversNothing to other methods', function () {
|
||||
$phpDoc = (new ReflectionClass($this))->getMethod($this->getName());
|
||||
|
||||
expect(str_contains($phpDoc->getDocComment(), '* @coversNothing'))->toBeFalse();
|
||||
});
|
||||
|
||||
it('throws exception if no class nor method has been found', function () {
|
||||
$testCall = new TestCall(TestSuite::getInstance(), 'filename', 'description', fn () => 'closure');
|
||||
|
||||
$testCall->covers('fakeName');
|
||||
})->throws(InvalidArgumentException::class, 'No class or method named "fakeName" has been found.');
|
||||
@ -14,10 +14,18 @@ it('catch exceptions and messages', function () {
|
||||
throw new Exception('Something bad happened');
|
||||
})->throws(Exception::class, 'Something bad happened');
|
||||
|
||||
it('catch exceptions, messages and code', function () {
|
||||
throw new Exception('Something bad happened', 1);
|
||||
})->throws(Exception::class, 'Something bad happened', 1);
|
||||
|
||||
it('can just define the message', function () {
|
||||
throw new Exception('Something bad happened');
|
||||
})->throws('Something bad happened');
|
||||
|
||||
it('can just define the code', function () {
|
||||
throw new Exception('Something bad happened', 1);
|
||||
})->throws(1);
|
||||
|
||||
it('not catch exceptions if given condition is false', function () {
|
||||
$this->assertTrue(true);
|
||||
})->throwsIf(false, Exception::class);
|
||||
@ -30,10 +38,22 @@ it('catch exceptions and messages if given condition is true', function () {
|
||||
throw new Exception('Something bad happened');
|
||||
})->throwsIf(true, Exception::class, 'Something bad happened');
|
||||
|
||||
it('catch exceptions, messages and code if given condition is true', function () {
|
||||
throw new Exception('Something bad happened', 1);
|
||||
})->throwsIf(true, Exception::class, 'Something bad happened', 1);
|
||||
|
||||
it('can just define the message if given condition is true', function () {
|
||||
throw new Exception('Something bad happened');
|
||||
})->throwsIf(true, 'Something bad happened');
|
||||
|
||||
it('can just define the code if given condition is true', function () {
|
||||
throw new Exception('Something bad happened', 1);
|
||||
})->throwsIf(true, 1);
|
||||
|
||||
it('can just define the message if given condition is 1', function () {
|
||||
throw new Exception('Something bad happened');
|
||||
})->throwsIf(1, 'Something bad happened');
|
||||
|
||||
it('can just define the code if given condition is 1', function () {
|
||||
throw new Exception('Something bad happened', 1);
|
||||
})->throwsIf(1, 1);
|
||||
|
||||
@ -12,7 +12,7 @@ class Number
|
||||
public function __construct(
|
||||
public int $value
|
||||
) {
|
||||
//..
|
||||
// ..
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ class Char
|
||||
public function __construct(
|
||||
public string $value
|
||||
) {
|
||||
//..
|
||||
// ..
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ class Symbol
|
||||
public function __construct(
|
||||
public string $value
|
||||
) {
|
||||
//..
|
||||
// ..
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,11 +69,11 @@ expect()->pipe('toBe', function ($next, $expected) use ($state) {
|
||||
assertInstanceOf(Char::class, $expected);
|
||||
assertEquals($this->value->value, $expected->value);
|
||||
|
||||
//returning nothing stops pipeline execution
|
||||
// returning nothing stops pipeline execution
|
||||
return;
|
||||
}
|
||||
|
||||
//calling $next(); let the pipeline to keep running
|
||||
// calling $next(); let the pipeline to keep running
|
||||
$next();
|
||||
});
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ use PHPUnit\Framework\ExpectationFailedException;
|
||||
expect(true)->toBeTrue()->and(false)->toBeFalse();
|
||||
|
||||
test('strict comparisons', function () {
|
||||
$nuno = new stdClass();
|
||||
$nuno = new stdClass();
|
||||
$dries = new stdClass();
|
||||
|
||||
expect($nuno)->toBe($nuno)->not->toBe($dries);
|
||||
|
||||
@ -3,24 +3,24 @@
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
|
||||
test('pass', function () {
|
||||
$object = new stdClass();
|
||||
$object = new stdClass();
|
||||
$object->name = 'Jhon';
|
||||
$object->age = 21;
|
||||
$object->age = 21;
|
||||
|
||||
expect($object)->toHaveProperties(['name', 'age']);
|
||||
});
|
||||
|
||||
test('failures', function () {
|
||||
$object = new stdClass();
|
||||
$object = new stdClass();
|
||||
$object->name = 'Jhon';
|
||||
|
||||
expect($object)->toHaveProperties(['name', 'age']);
|
||||
})->throws(ExpectationFailedException::class);
|
||||
|
||||
test('not failures', function () {
|
||||
$object = new stdClass();
|
||||
$object = new stdClass();
|
||||
$object->name = 'Jhon';
|
||||
$object->age = 21;
|
||||
$object->age = 21;
|
||||
|
||||
expect($object)->not->toHaveProperties(['name', 'age']);
|
||||
})->throws(ExpectationFailedException::class);
|
||||
|
||||
@ -58,3 +58,19 @@ test('closure missing parameter', function () {
|
||||
test('closure missing type-hint', function () {
|
||||
expect(function () {})->toThrow(function ($e) {});
|
||||
})->throws(InvalidArgumentException::class, 'The given closure\'s parameter must be type-hinted as the class string.');
|
||||
|
||||
it('can handle a non-defined exception', function () {
|
||||
expect(function () {
|
||||
throw new NonExistingException();
|
||||
})->toThrow(NonExistingException::class);
|
||||
})->throws(Error::class, 'Class "NonExistingException" not found');
|
||||
|
||||
it('can handle a class not found Error', function () {
|
||||
expect(function () {
|
||||
throw new NonExistingException();
|
||||
})->toThrow('Class "NonExistingException" not found');
|
||||
|
||||
expect(function () {
|
||||
throw new NonExistingException();
|
||||
})->toThrow(Error::class, 'Class "NonExistingException" not found');
|
||||
});
|
||||
|
||||
@ -3,9 +3,9 @@
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->unlessObject = new stdClass();
|
||||
$this->unlessObject = new stdClass();
|
||||
$this->unlessObject->trueValue = true;
|
||||
$this->unlessObject->foo = 'foo';
|
||||
$this->unlessObject->foo = 'foo';
|
||||
});
|
||||
|
||||
it('pass', function () {
|
||||
|
||||
@ -3,9 +3,9 @@
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->whenObject = new stdClass();
|
||||
$this->whenObject = new stdClass();
|
||||
$this->whenObject->trueValue = true;
|
||||
$this->whenObject->foo = 'foo';
|
||||
$this->whenObject->foo = 'foo';
|
||||
});
|
||||
|
||||
it('pass', function () {
|
||||
|
||||
@ -10,7 +10,7 @@ it('gets file name from closure', function () {
|
||||
});
|
||||
|
||||
it('gets property values', function () {
|
||||
$class = new class() {
|
||||
$class = new class() {
|
||||
private $foo = 'bar';
|
||||
};
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ use Pest\TestSuite;
|
||||
|
||||
it('does not allow to add the same test description twice', function () {
|
||||
$testSuite = new TestSuite(getcwd(), 'tests');
|
||||
$method = new TestCaseMethodFactory('foo', 'bar', null);
|
||||
$method = new TestCaseMethodFactory('foo', 'bar', null);
|
||||
|
||||
$testSuite->tests->set($method);
|
||||
$testSuite->tests->set($method);
|
||||
@ -43,7 +43,7 @@ it('can return an array of all test suite filenames', function () {
|
||||
it('can filter the test suite filenames to those with the only method', function () {
|
||||
$testSuite = new TestSuite(getcwd(), 'tests');
|
||||
|
||||
$testWithOnly = new TestCaseMethodFactory('a', 'b', null);
|
||||
$testWithOnly = new TestCaseMethodFactory('a', 'b', null);
|
||||
$testWithOnly->only = true;
|
||||
$testSuite->tests->set($testWithOnly);
|
||||
|
||||
@ -61,7 +61,7 @@ it('does not filter the test suite filenames to those with the only method when
|
||||
|
||||
$test = function () {};
|
||||
|
||||
$testWithOnly = new TestCaseMethodFactory('a', 'b', null);
|
||||
$testWithOnly = new TestCaseMethodFactory('a', 'b', null);
|
||||
$testWithOnly->only = true;
|
||||
$testSuite->tests->set($testWithOnly);
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ test('visual snapshot of help command output', function () {
|
||||
|
||||
if (getenv('REBUILD_SNAPSHOTS')) {
|
||||
$outputBuffer = new BufferedOutput();
|
||||
$plugin = new Help($outputBuffer);
|
||||
$plugin = new Help($outputBuffer);
|
||||
|
||||
$plugin();
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
test('visual snapshot of test suite on success', function () {
|
||||
$testsPath = dirname(__DIR__);
|
||||
$snapshot = implode(DIRECTORY_SEPARATOR, [
|
||||
$snapshot = implode(DIRECTORY_SEPARATOR, [
|
||||
$testsPath,
|
||||
'.snapshots',
|
||||
'success.txt',
|
||||
|
||||
Reference in New Issue
Block a user