Skip to content

Commit

Permalink
Call reveal() on local prophesized collaborators.
Browse files Browse the repository at this point in the history
  • Loading branch information
adamelso committed Jun 13, 2024
1 parent ae3a710 commit e0b300e
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 46 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/symfony.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ jobs:
# run: bin/phpunit --testdox

- name: 'Candidate conversion: Convert sylius/addressing unit tests from PhpSpec to PHPUnit, then run them'
run: php transunit.php vendor/sylius/addressing/spec && phpunit var/
run: php transunit.php vendor/sylius/addressing/spec var/sylius/addressing && phpunit var/sylius/addressing
5 changes: 2 additions & 3 deletions lib/Transunit/Pass/AssertionPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
/**
* ```
* - $this->_testSubject->contractOut(47)->shouldReturn($agent47);
* + static::assertSame($agent47, $this->_testSubject->contractOut(47));
* + self::assertSame($agent47, $this->_testSubject->contractOut(47));
* ```
*/
class AssertionPass implements Pass
Expand Down Expand Up @@ -55,15 +55,14 @@ public function rewrite(Node $node): void
$expectation = $node->expr->args[0]->value;
$call = $node->expr->var;


if (
$expectation instanceof Node\Expr\ConstFetch
&& isset($mappedConstantAssertions[$expectation->name->toString()])
) {
$assertionMethod = $mappedConstantAssertions[$expectation->name->toString()] ?? null;

$rewrittenAssertion = new Node\Expr\StaticCall(
new Node\Name('static'),
new Node\Name('self'),
$assertionMethod,
[
new Node\Arg($call)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use PhpParser\NodeFinder;
use Transunit\Pass;

class RevealPass implements Pass
class GlobalRevealPass implements Pass
{
public function find(NodeFinder $nodeFinder, $ast): array
{
Expand Down
57 changes: 57 additions & 0 deletions lib/Transunit/Pass/LocalRevealPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

namespace Transunit\Pass;

use PhpParser\Node;
use PhpParser\NodeFinder;
use PhpParser\NodeTraverser;
use Transunit\Pass;
use Transunit\Visitor\ParentConnectingVisitor;
use Transunit\Visitor\RevealCollaboratorVisitor;

class LocalRevealPass implements Pass
{
public function find(NodeFinder $nodeFinder, $ast): array
{
return $nodeFinder->find($ast, function (Node $node) {
if ($node instanceof Node\Stmt\ClassMethod && !in_array($node->name->toString(), ['setUp', 'let'],true)) {
return $node;
}

return null;
});
}

public function rewrite(Node $node): void
{
if (!$node instanceof Node\Stmt\ClassMethod) {
return;
}

$this->reveal($node);
}

private function reveal(Node\Stmt\ClassMethod $node): void
{
if (in_array($node->name->toString(), ['let', 'setUp'], true)) {
return;
}

$collabs = [];
foreach ($node->params as $param) {
$variableName = $param->var->name;
$collabs[] = $variableName;
}

if (empty($collabs)) {
return;
}

$subNodeTraverser = new NodeTraverser(
new ParentConnectingVisitor(),
new RevealCollaboratorVisitor($collabs)
);

$node->stmts = $subNodeTraverser->traverse($node->stmts);
}
}
38 changes: 1 addition & 37 deletions lib/Transunit/Pass/ProphesizeLocalCollaboratorsPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,8 @@ private function prophesizeLocalCollaborators(Node\Stmt\ClassMethod $node): void
return;
}

$localCollaborators = [];

foreach ($node->params as $param) {
$variableName = $param->var->name;
$localCollaborators[$variableName] = $param->type->toString();

array_unshift($node->stmts, new Node\Stmt\Expression(
new Node\Expr\Assign(
Expand All @@ -70,44 +67,11 @@ private function prophesizeLocalCollaborators(Node\Stmt\ClassMethod $node): void
'class'
)),
]
),
)
)
));
}

$node->params = [];

$nodeFinder = new NodeFinder();

// find all variables where the variable name matches the key within $localCollaborators,
// and modify it to call ->reveal() on the variable:

$args = $nodeFinder->findInstanceOf($node->stmts, Node\Arg::class);

$args = array_filter($args, function ($a) use ($localCollaborators) {
if (!$a->value instanceof Node\Expr\Variable) {
return false;
}

if (!array_key_exists($a->value->name, $localCollaborators)) {
return false;
}

return true;
});

if (empty($args)) {
return;
}

// call ->reveal() on the collaborator instance.
foreach ($args as $arg) {
$collaboratorVariable = $arg->value;
$arg->value = new Node\Expr\MethodCall(
$collaboratorVariable,
'reveal',
[]
);
}
}
}
10 changes: 6 additions & 4 deletions lib/Transunit/Transunit.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ public static function run(string $path, string $destination = 'var'): void
$root = dirname(__DIR__, 2);
$exportDir = "{$root}/{$destination}";

$fs->remove($exportDir);
// @todo Confirm filesystem changes with user.
// $fs->remove($exportDir);
$fs->mkdir($exportDir);

foreach ($specs as $file) {
Expand Down Expand Up @@ -61,19 +62,20 @@ private static function processFile(string $path): string
new Pass\RenameClassPass(),
new Pass\ChangeExtendedClassPass(),
new Pass\DeclareTestSubjectPropertyPass(),
new Pass\CreateSetupIfNoneExistsPass(), // run after DeclareTestSubjectPropertyPass and before UseProphecyTraitPass
new Pass\CreateSetupIfNoneExistsPass(), // run after DeclareTestSubjectPropertyPass
new Pass\UseProphecyTraitPass(), // run after CreateSetupIfNoneExistsPass
new Pass\RenameSetupPass(),
new Pass\AddTestMethodPrefixPass(),
new Pass\InitializeTestSubjectPass(),
new Pass\RevealPass(),
new Pass\GlobalRevealPass(),
new Pass\CallTestSubjectPass(),
new Pass\AssertionPass(), // run after CallTestSubjectPass.
new Pass\ExceptionAssertionPass(),
new Pass\DeclareGlobalCollaboratorPass(),
new Pass\ProphesizeGlobalCollaboratorsPass(),
new Pass\CallGlobalCollaboratorPass(), // run after ProphesizeGlobalCollaboratorsPass
new Pass\ProphesizeLocalCollaboratorsPass(), // run after ProphesizeGlobalCollaboratorsPass
new Pass\LocalRevealPass(), // run after ProphesizeGlobalCollaboratorsPass
new Pass\ProphesizeLocalCollaboratorsPass(), // run after LocalRevealPass
new Pass\TestSubjectAsArgumentPass(),
];

Expand Down
26 changes: 26 additions & 0 deletions lib/Transunit/Visitor/ParentConnectingVisitor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Transunit\Visitor;

use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;

class ParentConnectingVisitor extends NodeVisitorAbstract
{
public function enterNode(Node $node): void
{
foreach ($node->getSubNodeNames() as $name) {
$subNode = $node->{$name};

if (is_array($subNode)) {
foreach ($subNode as $childNode) {
if ($childNode instanceof Node) {
$childNode->setAttribute('parent', $node);
}
}
} elseif ($subNode instanceof Node) {
$subNode->setAttribute('parent', $node);
}
}
}
}
62 changes: 62 additions & 0 deletions lib/Transunit/Visitor/RevealCollaboratorVisitor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace Transunit\Visitor;

use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;

/**
* Finds local collaborators within a test method / spec example that should have their
* prophecy revealed.
*
* For example, in PhpSpec you may have the following:
*
* function it_listens_to_request_events(Event $event)
* {
* $this->onKernelRequest($event);
* }
*
* when translated to PHPUnit by the ProphesizeLocalCollaboratorsPass would be:
*
* $event = $this->prophesize(Event::class);
* $this->_testSubject->onKernelRequest($event);
*
* The visitor will modify this statement to call reveal() on the 'event' collaborator:
*
* $this->_testSubject->onKernelRequest($event->reveal());
*
* @see ProphesizeLocalCollaboratorsPass
*/
class RevealCollaboratorVisitor extends NodeVisitorAbstract
{
/**
* @var string[] This is the variable name of the local collaborator that should be revealed.
*/
private $collaborators;

public function __construct(array $collaborators)
{
$this->collaborators = $collaborators;
}

public function leaveNode(Node $node)
{
if (! $node instanceof Node\Expr\Variable) {
return $node;
}

if (! in_array($node->name, $this->collaborators, true)) {
return $node;
}

/** @see ParentConnectingVisitor which sets this attribute prior to this visitor being invoked by the traverser. */
if ($node->getAttribute('parent') instanceof Node\Expr\MethodCall) {
return $node;
}

return new Node\Expr\MethodCall(
$node,
'reveal'
);
}
}

0 comments on commit e0b300e

Please sign in to comment.