Skip to content

Commit

Permalink
Create a HaveCorrespondingUnit 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 authored and hgraca committed Aug 7, 2023
1 parent 976c200 commit 0099166
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 0 deletions.
57 changes: 57 additions & 0 deletions src/Expression/ForClasses/HaveCorrespondingUnit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?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;

final class HaveCorrespondingUnit 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 unit 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());
}
}
9 changes: 9 additions & 0 deletions tests/Unit/Expressions/ForClasses/DummyClasses/Cat.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

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

final readonly class Cat
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

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

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

declare(strict_types=1);

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

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

declare(strict_types=1);

namespace Arkitect\Tests\Unit\Expressions\ForClasses;

use Arkitect\Analyzer\ClassDescription;
use Arkitect\Analyzer\FullyQualifiedClassName;
use Arkitect\Expression\ForClasses\HaveCorrespondingUnit;
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 HaveCorrespondingUnitTest 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 HaveCorrespondingUnit(
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 HaveCorrespondingUnit(
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 unit named: '
."'Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses\DogTestCase' because $because",
$violationError
);
}
}

0 comments on commit 0099166

Please sign in to comment.