Skip to content

Commit 85eef97

Browse files
author
Herberto Graca
committed
Create a HasCorrespondingUnit expression
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.
1 parent 976c200 commit 85eef97

File tree

5 files changed

+166
-0
lines changed

5 files changed

+166
-0
lines changed

Diff for: src/Expression/ForClasses/HasCorrespondingUnit.php

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Expression\ForClasses;
6+
7+
use Arkitect\Analyzer\ClassDescription;
8+
use Arkitect\Expression\Description;
9+
use Arkitect\Expression\Expression;
10+
use Arkitect\Rules\Violation;
11+
use Arkitect\Rules\Violations;
12+
use Closure;
13+
14+
final class HasCorrespondingUnit implements Expression
15+
{
16+
/** @var Closure */
17+
private $inferFqnFunction;
18+
19+
public function __construct(Closure $inferFqnFunction)
20+
{
21+
$this->inferFqnFunction = $inferFqnFunction;
22+
}
23+
24+
public function describe(ClassDescription $theClass, string $because): Description
25+
{
26+
$correspondingFqn = $this->inferCorrespondingFqn($theClass);
27+
28+
return new Description("should have a matching test class named: '$correspondingFqn'", $because);
29+
}
30+
31+
public function evaluate(ClassDescription $theClass, Violations $violations, string $because): void
32+
{
33+
$correspondingFqn = $this->inferCorrespondingFqn($theClass);
34+
35+
if (
36+
!trait_exists($correspondingFqn)
37+
&& !class_exists($correspondingFqn)
38+
&& !interface_exists($correspondingFqn)
39+
) {
40+
$violations->add(
41+
new Violation(
42+
$theClass->getFQCN(),
43+
$this->describe($theClass, $because)->toString()
44+
)
45+
);
46+
}
47+
}
48+
49+
/**
50+
* @return class-string
51+
*/
52+
public function inferCorrespondingFqn(ClassDescription $theClass): string
53+
{
54+
$inferFqn = $this->inferFqnFunction;
55+
56+
return $inferFqn($theClass->getFQCN());
57+
}
58+
}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses;
6+
7+
final readonly class Cat
8+
{
9+
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses;
6+
7+
final readonly class CatTestCase
8+
{
9+
10+
}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses;
6+
7+
final class Dog
8+
{
9+
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Unit\Expressions\ForClasses;
6+
7+
use Arkitect\Analyzer\ClassDescription;
8+
use Arkitect\Analyzer\FullyQualifiedClassName;
9+
use Arkitect\Expression\ForClasses\HasCorrespondingUnit;
10+
use Arkitect\Rules\Violations;
11+
use Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses\Cat;
12+
use Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses\Dog;
13+
use PHPUnit\Framework\TestCase;
14+
15+
class HasCorrespondingUnitTest extends TestCase
16+
{
17+
public function test_it_should_pass_the_validation(): void
18+
{
19+
$class = Cat::class;
20+
$classDescription = new ClassDescription(
21+
FullyQualifiedClassName::fromString($class),
22+
[],
23+
[],
24+
null,
25+
false,
26+
false,
27+
false,
28+
false,
29+
false
30+
);
31+
$constraint = new HasCorrespondingUnit(
32+
function ($fqn) {
33+
return $fqn . 'TestCase';
34+
}
35+
);
36+
37+
$because = 'we want all our command handlers to have a test';
38+
$violations = new Violations();
39+
$constraint->evaluate($classDescription, $violations, $because);
40+
41+
self::assertEquals(0, $violations->count());
42+
}
43+
44+
public function test_it_should_return_violation_error(): void
45+
{
46+
$class = Dog::class;
47+
$classDescription = new ClassDescription(
48+
FullyQualifiedClassName::fromString($class),
49+
[],
50+
[],
51+
null,
52+
false,
53+
false,
54+
false,
55+
false,
56+
false
57+
);
58+
$constraint = new HasCorrespondingUnit(
59+
function ($fqn) {
60+
return $fqn . 'TestCase';
61+
}
62+
);
63+
64+
$because = 'we want all our command handlers to have a test';
65+
$violations = new Violations();
66+
$constraint->evaluate($classDescription, $violations, $because);
67+
68+
self::assertNotEquals(0, $violations->count());
69+
70+
$violationError = $constraint->describe($classDescription, $because)->toString();
71+
$this->assertEquals(
72+
"should have a matching test class named: "
73+
. "'Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses\DogTestCase' because $because",
74+
$violationError
75+
);
76+
}
77+
78+
}

0 commit comments

Comments
 (0)