Skip to content

Commit 66a9442

Browse files
Herberto Gracahgraca
Herberto Graca
authored andcommitted
Create a HaveCorrespondingUnit 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 66a9442

File tree

6 files changed

+180
-0
lines changed

6 files changed

+180
-0
lines changed

README.md

+19
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,25 @@ For example: `phparkitect debug:expression ResideInOneOfTheseNamespaces App`
152152

153153
Currently, you can check if a class:
154154

155+
### Has a corresponding code unit in another namespace
156+
157+
This will allow us to ensure that certain classes always have a test,
158+
or that every test has a matching class and their namespaces are correct.
159+
160+
```php
161+
$rules = Rule::allClasses()
162+
->that(new ResideInOneOfTheseNamespaces('App\Core\Component\**\Command\*'))
163+
->should(new HaveCorrespondingUnit(
164+
// This will assert that class `App\Core\Component\MyComponent\Command\MyCommand`
165+
// has a test class in `Tests\App\Core\Component\MyComponent\Command\MyCommandTest`
166+
function ($fqcn) {
167+
return 'Tests\\'.$fqcn.'Test';
168+
}
169+
)
170+
)
171+
->because('we want all our command handlers to have a test');
172+
```
173+
155174
### Depends on a namespace
156175

157176
```php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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+
13+
final class HaveCorrespondingUnit implements Expression
14+
{
15+
/** @var \Closure */
16+
private $inferFqnFunction;
17+
18+
public function __construct(\Closure $inferFqnFunction)
19+
{
20+
$this->inferFqnFunction = $inferFqnFunction;
21+
}
22+
23+
public function describe(ClassDescription $theClass, string $because): Description
24+
{
25+
$correspondingFqn = $this->inferCorrespondingFqn($theClass);
26+
27+
return new Description("should have a matching unit named: '$correspondingFqn'", $because);
28+
}
29+
30+
public function evaluate(ClassDescription $theClass, Violations $violations, string $because): void
31+
{
32+
$correspondingFqn = $this->inferCorrespondingFqn($theClass);
33+
34+
if (
35+
!trait_exists($correspondingFqn)
36+
&& !class_exists($correspondingFqn)
37+
&& !interface_exists($correspondingFqn)
38+
) {
39+
$violations->add(
40+
new Violation(
41+
$theClass->getFQCN(),
42+
$this->describe($theClass, $because)->toString()
43+
)
44+
);
45+
}
46+
}
47+
48+
/**
49+
* @return class-string
50+
*/
51+
public function inferCorrespondingFqn(ClassDescription $theClass): string
52+
{
53+
$inferFqn = $this->inferFqnFunction;
54+
55+
return $inferFqn($theClass->getFQCN());
56+
}
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses;
6+
7+
final class Cat
8+
{
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses;
6+
7+
final class CatTestCase
8+
{
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses;
6+
7+
final class Dog
8+
{
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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\HaveCorrespondingUnit;
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 HaveCorrespondingUnitTest 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 HaveCorrespondingUnit(
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 HaveCorrespondingUnit(
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 unit named: '
73+
."'Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses\DogTestCase' because $because",
74+
$violationError
75+
);
76+
}
77+
}

0 commit comments

Comments
 (0)