Skip to content

Commit

Permalink
feat(Testing): Add ability to generate test Assert for all contracts …
Browse files Browse the repository at this point in the history
…methods
  • Loading branch information
pionl committed Nov 15, 2022
1 parent 36197b4 commit 1d95b3e
Show file tree
Hide file tree
Showing 77 changed files with 2,544 additions and 102 deletions.
7 changes: 5 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
},
"require-dev": {
"mockery/mockery": "^1.5.0",
"nette/php-generator": "v4.0.3",
"nette/php-generator": "v4.0.5",
"nikic/php-parser": "v4.15.1",
"nunomaduro/larastan": "2.2.0",
"orchestra/testbench": "^v7.8.0",
"phpstan/phpdoc-parser": "^1.13",
"phpstan/phpstan": "1.8.6",
"phpstan/phpstan-deprecation-rules": "^1.0.0",
"phpstan/phpstan-mockery": "^1.1.0",
Expand Down Expand Up @@ -58,7 +59,9 @@
"suggest": {
"php-http/mock-client": "Use this package for testing HTTP/s clients.",
"wrk-flow/php-api-sdk-builder": "Wrap external APIs with type safe API client",
"wrk-flow/php-get-type-value": "Get values from array/xml in type safe maner"
"wrk-flow/php-get-type-value": "Get values from array/xml in type safe maner",
"nette/php-generator": "For generating test exceptions / assert classes",
"phpstan/phpdoc-parser": "For better generation of test exceptions (with phpdoc typehints)"
},
"extra": {
"laravel": {
Expand Down
5 changes: 5 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ parameters:
count: 1
path: src/Testing/Actions/GetNamespaceForStubsAction.php

-
message: "#^Calling PHPStan\\\\PhpDoc\\\\PhpDocStringResolver\\:\\:resolve\\(\\) is not covered by backward compatibility promise\\. The method might change in a minor PHPStan version\\.$#"
count: 1
path: src/Testing/Actions/ParsePhpDocAction.php

-
message: "#^Method Tests\\\\LaraStrict\\\\Feature\\\\Translations\\\\InvalidServiceProviderTranslations\\:\\:getProviderClass\\(\\) should return class\\-string\\<LaraStrict\\\\Providers\\\\AbstractServiceProvider\\> but returns string\\.$#"
count: 1
Expand Down
82 changes: 82 additions & 0 deletions src/Testing/AbstractExpectationCallsMap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

declare(strict_types=1);

namespace LaraStrict\Testing;

use LogicException;

abstract class AbstractExpectationCallsMap
{
/**
* @var array<class-string<object>, array<object>>
*/
private array $_expectationMap = [];

/**
* @var array<class-string<object>, int>
*/
private array $_callStep = [];

/**
* Contains current call number.
*/
private int $_currentDebugStep = 0;

public function addExpectation(object $expectation): self
{
$this->_expectationMap[$expectation::class][] = $expectation;

return $this;
}

/**
* @template TExpectation
* @param class-string<TExpectation> $class
* @param array<TExpectation> $expectations
*/
public function setExpectations(string $class, array $expectations): self
{
$this->_expectationMap[$class] = $expectations;

return $this;
}

/**
* @template TExpectation of object
*
* @param class-string<TExpectation> $class
*
* @return TExpectation
*/
protected function getExpectation(string $class): object
{
$map = $this->_expectationMap[$class] ?? [];
$callStep = $this->_callStep[$class] ?? 0;

$this->_currentDebugStep = $callStep + 1;

if (array_key_exists($callStep, $map) === false) {
throw new LogicException($this->getDebugMessage($this->_currentDebugStep, 'not set', 2));
}

/** @var TExpectation $expectation */
$expectation = $map[$callStep];
$this->_callStep[$class] = $this->_currentDebugStep;

return $expectation;
}

protected function getDebugMessage(int $callStep = null, string $reason = 'failed', int $debugLevel = 1): string
{
$caller = debug_backtrace()[$debugLevel];

return sprintf(
'Expectation for [%s@%s] %s for a n (%s) call',
$caller['class'] ?? $this::class,
$caller['function'],
$reason,
$callStep ?? $this->_currentDebugStep
);
}
}
12 changes: 8 additions & 4 deletions src/Testing/Actions/GetDevNamespaceForStubsAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@

class GetDevNamespaceForStubsAction implements GetNamespaceForStubsActionContract
{
public function execute(Command $command, string $basePath): NamespaceEntity
public function execute(Command $command, string $basePath, string $inputClass): NamespaceEntity
{
// We want to place Laravel assert / expectations to Laravel Folder.

$subFolder = str_starts_with($inputClass, 'Illuminate' . StubConstants::NameSpaceSeparator) ? 'Laravel' : null;
return new NamespaceEntity(
'src' . DIRECTORY_SEPARATOR . 'Testing' . DIRECTORY_SEPARATOR,
implode(StubConstants::NameSpaceSeparator, [
implode(DIRECTORY_SEPARATOR, array_filter(['src', 'Testing', $subFolder])) . DIRECTORY_SEPARATOR,
implode(StubConstants::NameSpaceSeparator, array_filter([
'LaraStrict',
'Testing',
]) . StubConstants::NameSpaceSeparator
$subFolder,
])) . StubConstants::NameSpaceSeparator
);
}
}
2 changes: 1 addition & 1 deletion src/Testing/Actions/GetNamespaceForStubsAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public function __construct(
) {
}

public function execute(Command $command, string $basePath): NamespaceEntity
public function execute(Command $command, string $basePath, string $inputClass): NamespaceEntity
{
// Ask for which namespace which to use for "tests"
$composer = $this->getComposerJsonData($basePath);
Expand Down
57 changes: 57 additions & 0 deletions src/Testing/Actions/ParsePhpDocAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace LaraStrict\Testing\Actions;

use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Contracts\Container\Container;
use LaraStrict\Testing\Entities\PhpDocEntity;
use LaraStrict\Testing\Enums\PhpType;
use PHPStan\PhpDoc\PhpDocStringResolver;
use ReflectionMethod;

class ParsePhpDocAction
{
private ?PhpDocStringResolver $phpDocStringResolver = null;

public function __construct(Container $container)
{
if (class_exists(PhpDocStringResolver::class)) {
try {
$this->phpDocStringResolver = $container->make(PhpDocStringResolver::class);
} catch (BindingResolutionException) {
// Package phpstan/phpdoc-parser not installed
}
}
}

public function execute(ReflectionMethod $method): PhpDocEntity
{
if ($this->phpDocStringResolver === null) {
return new PhpDocEntity();
}

$comment = $method->getDocComment();

if ($comment === false) {
return new PhpDocEntity();
}

$doc = $this->phpDocStringResolver->resolve($comment);

$returnTags = $doc->getReturnTagValues();
$returnType = PhpType::Unknown;

if ($returnTags !== []) {
$name = (string) $returnTags[0]->type;
$returnType = match ($name) {
'$this', 'self', 'static' => PhpType::Self,
'void' => PhpType::Void,
default => PhpType::Mixed,
};
}

return new PhpDocEntity(returnType: $returnType);
}
}
Loading

0 comments on commit 1d95b3e

Please sign in to comment.