feat: toHavePrivateMethodsBesides, toHaveProtectedMethodsBesides, toHavePublicMethodsBesides

This commit is contained in:
Nuno Maduro
2024-08-09 00:24:24 +01:00
parent 347bcfd8a8
commit b6bf01148f
14 changed files with 196 additions and 11 deletions

View File

@ -31,14 +31,15 @@ final class Laravel extends AbstractPreset
->not->toImplement(Throwable::class) ->not->toImplement(Throwable::class)
->ignoring('App\Exceptions'); ->ignoring('App\Exceptions');
$this->expectations[] = expect('App\Http\Controllers')
->classes()
->toHaveSuffix('Controller');
$this->expectations[] = expect('App') $this->expectations[] = expect('App')
->not->toHaveSuffix('Controller') ->not->toHaveSuffix('Controller')
->ignoring('App\Http\Controllers'); ->ignoring('App\Http\Controllers');
$this->expectations[] = expect('App\Http\Controllers')
->classes()
->toHaveSuffix('Controller')
->not->toHavePublicMethodsBesides(['__construct', '__invoke', 'index', 'show', 'create', 'store', 'edit', 'update', 'destroy']);
$this->expectations[] = expect('App\Http\Middleware') $this->expectations[] = expect('App\Http\Middleware')
->classes() ->classes()
->toHaveMethod('handle'); ->toHaveMethod('handle');

View File

@ -20,6 +20,7 @@ final class Relaxed extends AbstractPreset
$this->eachUserNamespace( $this->eachUserNamespace(
fn (Expectation $namespace): ArchExpectation => $namespace->not->toUseStrictTypes(), fn (Expectation $namespace): ArchExpectation => $namespace->not->toUseStrictTypes(),
fn (Expectation $namespace): ArchExpectation => $namespace->classes()->not->toBeFinal(), fn (Expectation $namespace): ArchExpectation => $namespace->classes()->not->toBeFinal(),
fn (Expectation $namespace): ArchExpectation => $namespace->classes()->not->toHavePrivateMethodsBesides([]),
); );
} }
} }

View File

@ -21,6 +21,7 @@ final class Strict extends AbstractPreset
fn (Expectation $namespace): ArchExpectation => $namespace->toUseStrictTypes(), fn (Expectation $namespace): ArchExpectation => $namespace->toUseStrictTypes(),
fn (Expectation $namespace): ArchExpectation => $namespace->classes()->toBeFinal(), fn (Expectation $namespace): ArchExpectation => $namespace->classes()->toBeFinal(),
fn (Expectation $namespace): ArchExpectation => $namespace->classes()->not->toBeAbstract(), fn (Expectation $namespace): ArchExpectation => $namespace->classes()->not->toBeAbstract(),
fn (Expectation $namespace): ArchExpectation => $namespace->classes()->not->toHaveProtectedMethodsBesides([])
); );
$this->expectations[] = expect([ $this->expectations[] = expect([

View File

@ -588,6 +588,30 @@ final class Expectation
); );
} }
/**
* Not supported.
*/
public function toHavePublicMethodsBesides(): never
{
throw InvalidExpectation::fromMethods(['toHavePublicMethodsBesides']);
}
/**
* Not supported.
*/
public function toHaveProtectedMethodsBesides(): never
{
throw InvalidExpectation::fromMethods(['toHaveProtectedMethodsBesides']);
}
/**
* Not supported.
*/
public function toHavePrivateMethodsBesides(): never
{
throw InvalidExpectation::fromMethods(['toHavePrivateMethodsBesides']);
}
/** /**
* Asserts that the given expectation target is enum. * Asserts that the given expectation target is enum.
*/ */

View File

@ -224,6 +224,93 @@ final class OppositeExpectation
); );
} }
/**
* Asserts that the given expectation target not to have the public methods besides the given methods.
*/
public function toHavePublicMethodsBesides(array|string $methods): ArchExpectation
{
$methods = is_array($methods) ? $methods : [$methods];
return Targeted::make(
$this->original,
function (ObjectDescription $object) use ($methods): bool {
$reflectionMethods = isset($object->reflectionClass)
? Reflection::getMethodsFromReflectionClass($object->reflectionClass, ReflectionMethod::IS_PUBLIC)
: [];
foreach ($reflectionMethods as $reflectionMethod) {
if (! in_array($reflectionMethod->name, $methods, true)) {
return false;
}
}
return true;
},
count($methods) === 0
? 'not to have public methods'
: sprintf("not to have public methods besides '%s'", implode("', '", $methods)),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'public function')),
);
}
/**
* Asserts that the given expectation target not to have the protected methods besides the given methods.
*/
public function toHaveProtectedMethodsBesides(array|string $methods): ArchExpectation
{
$methods = is_array($methods) ? $methods : [$methods];
return Targeted::make(
$this->original,
function (ObjectDescription $object) use ($methods): bool {
$reflectionMethods = isset($object->reflectionClass)
? Reflection::getMethodsFromReflectionClass($object->reflectionClass, ReflectionMethod::IS_PROTECTED)
: [];
foreach ($reflectionMethods as $reflectionMethod) {
if (! in_array($reflectionMethod->name, $methods, true)) {
return false;
}
}
return true;
},
count($methods) === 0
? 'not to have protected methods'
: sprintf("not to have protected methods besides '%s'", implode("', '", $methods)),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'protected function')),
);
}
/**
* Asserts that the given expectation target not to have the private methods besides the given methods.
*/
public function toHavePrivateMethodsBesides(array|string $methods): ArchExpectation
{
$methods = is_array($methods) ? $methods : [$methods];
return Targeted::make(
$this->original,
function (ObjectDescription $object) use ($methods): bool {
$reflectionMethods = isset($object->reflectionClass)
? Reflection::getMethodsFromReflectionClass($object->reflectionClass, ReflectionMethod::IS_PRIVATE)
: [];
foreach ($reflectionMethods as $reflectionMethod) {
if (! in_array($reflectionMethod->name, $methods, true)) {
return false;
}
}
return true;
},
count($methods) === 0
? 'not to have private methods'
: sprintf("not to have private methods besides '%s'", implode("', '", $methods)),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'private function')),
);
}
/** /**
* Asserts that the given expectation target is not enum. * Asserts that the given expectation target is not enum.
*/ */

View File

@ -19,7 +19,7 @@ abstract class Subscriber // @pest-arch-ignore-line
/** /**
* Creates a new TeamCityLogger instance. * Creates a new TeamCityLogger instance.
*/ */
final protected function logger(): TeamCityLogger final protected function logger(): TeamCityLogger // @pest-arch-ignore-line
{ {
return $this->logger; return $this->logger;
} }

View File

@ -11,7 +11,7 @@ final class CleanConsoleOutput extends ConsoleOutput
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected function doWrite(string $message, bool $newline): void protected function doWrite(string $message, bool $newline): void // @pest-arch-ignore-line
{ {
if ($this->isOpeningHeadline($message)) { if ($this->isOpeningHeadline($message)) {
return; return;

View File

@ -258,12 +258,12 @@ final class Reflection
* @param ReflectionClass<object> $reflectionClass * @param ReflectionClass<object> $reflectionClass
* @return array<int, ReflectionMethod> * @return array<int, ReflectionMethod>
*/ */
public static function getMethodsFromReflectionClass(ReflectionClass $reflectionClass): array public static function getMethodsFromReflectionClass(ReflectionClass $reflectionClass, int $filter = ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED | ReflectionMethod::IS_PRIVATE): array
{ {
$getMethods = fn (ReflectionClass $reflectionClass): array => array_filter( $getMethods = fn (ReflectionClass $reflectionClass): array => array_filter(
array_map( array_map(
fn (ReflectionMethod $method): \ReflectionMethod => $method, fn (ReflectionMethod $method): \ReflectionMethod => $method,
$reflectionClass->getMethods(), $reflectionClass->getMethods($filter),
), fn (ReflectionMethod $method): bool => $method->getDeclaringClass()->getName() === $reflectionClass->getName(), ), fn (ReflectionMethod $method): bool => $method->getDeclaringClass()->getName() === $reflectionClass->getName(),
); );

View File

@ -1,6 +1,6 @@
PASS Tests\Arch PASS Tests\Arch
✓ arch "base" preset ✓ arch "php" preset
✓ arch "strict" preset ✓ arch "strict" preset
✓ arch "security" preset ✓ arch "security" preset
✓ globals ✓ globals
@ -831,6 +831,10 @@
✓ opposite missing prefix ✓ opposite missing prefix
✓ opposite has prefix ✓ opposite has prefix
PASS Tests\Features\Expect\toHavePrivateMethodsBesides
✓ pass
✓ failures
PASS Tests\Features\Expect\toHaveProperties PASS Tests\Features\Expect\toHaveProperties
✓ pass ✓ pass
✓ failures ✓ failures
@ -849,6 +853,14 @@
✓ failures with message and Any matcher ✓ failures with message and Any matcher
✓ not failures ✓ not failures
PASS Tests\Features\Expect\toHaveProtectedMethodsBesides
✓ pass
✓ failures
PASS Tests\Features\Expect\toHavePublicMethodsBesides
✓ pass
✓ failures
PASS Tests\Features\Expect\toHaveSameSize PASS Tests\Features\Expect\toHaveSameSize
✓ failures with wrong type ✓ failures with wrong type
✓ pass ✓ pass
@ -1547,4 +1559,4 @@
WARN Tests\Visual\Version WARN Tests\Visual\Version
- visual snapshot of help command output - visual snapshot of help command output
Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 17 todos, 24 skipped, 1078 passed (2632 assertions) Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 17 todos, 24 skipped, 1084 passed (2644 assertions)

View File

@ -0,0 +1,12 @@
<?php
use Pest\Arch\Exceptions\ArchExpectationFailedException;
use Tests\Fixtures\Arch\ToHavePublicMethodsBesides\UserController;
test('pass', function () {
expect(UserController::class)->not->toHavePrivateMethodsBesides(['privateMethod']);
});
test('failures', function () {
expect(UserController::class)->not->toHavePrivateMethodsBesides([]);
})->throws(ArchExpectationFailedException::class);

View File

@ -0,0 +1,12 @@
<?php
use Pest\Arch\Exceptions\ArchExpectationFailedException;
use Tests\Fixtures\Arch\ToHavePublicMethodsBesides\UserController;
test('pass', function () {
expect(UserController::class)->not->toHaveProtectedMethodsBesides(['protectedMethod']);
});
test('failures', function () {
expect(UserController::class)->not->toHaveProtectedMethodsBesides([]);
})->throws(ArchExpectationFailedException::class);

View File

@ -0,0 +1,12 @@
<?php
use Pest\Arch\Exceptions\ArchExpectationFailedException;
use Tests\Fixtures\Arch\ToHavePublicMethodsBesides\UserController;
test('pass', function () {
expect(UserController::class)->not->toHavePublicMethodsBesides(['publicMethod']);
});
test('failures', function () {
expect(UserController::class)->not->toHavePublicMethodsBesides([]);
})->throws(ArchExpectationFailedException::class);

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Tests\Fixtures\Arch\ToHavePublicMethodsBesides;
class UserController
{
public function publicMethod(): string
{
return '';
}
protected function protectedMethod(): string
{
return '';
}
private function privateMethod(): string
{
return '';
}
}

View File

@ -16,7 +16,7 @@ $run = function () {
test('parallel', function () use ($run) { test('parallel', function () use ($run) {
expect($run('--exclude-group=integration')) expect($run('--exclude-group=integration'))
->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 17 todos, 19 skipped, 1064 passed (2602 assertions)') ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 17 todos, 19 skipped, 1070 passed (2612 assertions)')
->toContain('Parallel: 3 processes'); ->toContain('Parallel: 3 processes');
})->skipOnWindows(); })->skipOnWindows();