Skip to content

Commit

Permalink
Create a HasCorrespondingUnit expression
Browse files Browse the repository at this point in the history
This will allow us to ensure that certain classes always have a test,
or that every test has a matching class and their namespaces are correct.
  • Loading branch information
Herberto Graca committed Jul 26, 2023
1 parent 976c200 commit 85eef97
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 0 deletions.
58 changes: 58 additions & 0 deletions src/Expression/ForClasses/HasCorrespondingUnit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

declare(strict_types=1);

namespace Arkitect\Expression\ForClasses;

use Arkitect\Analyzer\ClassDescription;
use Arkitect\Expression\Description;
use Arkitect\Expression\Expression;
use Arkitect\Rules\Violation;
use Arkitect\Rules\Violations;
use Closure;

final class HasCorrespondingUnit implements Expression
{
/** @var Closure */
private $inferFqnFunction;

public function __construct(Closure $inferFqnFunction)
{
$this->inferFqnFunction = $inferFqnFunction;
}

public function describe(ClassDescription $theClass, string $because): Description
{
$correspondingFqn = $this->inferCorrespondingFqn($theClass);

return new Description("should have a matching test class named: '$correspondingFqn'", $because);
}

public function evaluate(ClassDescription $theClass, Violations $violations, string $because): void
{
$correspondingFqn = $this->inferCorrespondingFqn($theClass);

if (
!trait_exists($correspondingFqn)
&& !class_exists($correspondingFqn)
&& !interface_exists($correspondingFqn)
) {
$violations->add(
new Violation(
$theClass->getFQCN(),
$this->describe($theClass, $because)->toString()
)
);
}
}

/**
* @return class-string
*/
public function inferCorrespondingFqn(ClassDescription $theClass): string
{
$inferFqn = $this->inferFqnFunction;

return $inferFqn($theClass->getFQCN());
}
}
10 changes: 10 additions & 0 deletions tests/Unit/Expressions/ForClasses/DummyClasses/Cat.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses;

final readonly class Cat
{

}
10 changes: 10 additions & 0 deletions tests/Unit/Expressions/ForClasses/DummyClasses/CatTestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses;

final readonly class CatTestCase
{

}
10 changes: 10 additions & 0 deletions tests/Unit/Expressions/ForClasses/DummyClasses/Dog.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses;

final class Dog
{

}
78 changes: 78 additions & 0 deletions tests/Unit/Expressions/ForClasses/HasCorrespondingUnitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

namespace Arkitect\Tests\Unit\Expressions\ForClasses;

use Arkitect\Analyzer\ClassDescription;
use Arkitect\Analyzer\FullyQualifiedClassName;
use Arkitect\Expression\ForClasses\HasCorrespondingUnit;
use Arkitect\Rules\Violations;
use Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses\Cat;
use Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses\Dog;
use PHPUnit\Framework\TestCase;

class HasCorrespondingUnitTest extends TestCase
{
public function test_it_should_pass_the_validation(): void
{
$class = Cat::class;
$classDescription = new ClassDescription(
FullyQualifiedClassName::fromString($class),
[],
[],
null,
false,
false,
false,
false,
false
);
$constraint = new HasCorrespondingUnit(
function ($fqn) {
return $fqn . 'TestCase';
}
);

$because = 'we want all our command handlers to have a test';
$violations = new Violations();
$constraint->evaluate($classDescription, $violations, $because);

self::assertEquals(0, $violations->count());
}

public function test_it_should_return_violation_error(): void
{
$class = Dog::class;
$classDescription = new ClassDescription(
FullyQualifiedClassName::fromString($class),
[],
[],
null,
false,
false,
false,
false,
false
);
$constraint = new HasCorrespondingUnit(
function ($fqn) {
return $fqn . 'TestCase';
}
);

$because = 'we want all our command handlers to have a test';
$violations = new Violations();
$constraint->evaluate($classDescription, $violations, $because);

self::assertNotEquals(0, $violations->count());

$violationError = $constraint->describe($classDescription, $because)->toString();
$this->assertEquals(
"should have a matching test class named: "
. "'Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses\DogTestCase' because $because",
$violationError
);
}

}

0 comments on commit 85eef97

Please sign in to comment.