Skip to content

Rules

A rule is a statement, consisting in Selectors and Assertions, that must be true for the test to pass.

Each test class can contain one or more rules.

image-layered

Your rules must be public methods that start with test_ or apply the #[TestRule] attribute. You can build rules using the \PHPat\Test\PHPat::rule() method:

namespace App\Tests\Architecture;

use PHPat\Selector\Selector;
use PHPat\Test\Attributes\TestRule;
use PHPat\Test\Builder\Rule;
use PHPat\Test\PHPat;

final class ConfigurationTest
{
    public function test_domain_independence(): Rule
    {
        return PHPat::rule()
            ->classes(Selector::inNamespace('App\Domain'))
            ->canOnlyDependOn()
            ->classes(Selector::inNamespace('App\Domain'));
        ;
    }

    #[TestRule]
    public function entities_are_final(): Rule
    {
        return PHPat::rule()
            ->classes(Selector::extends(Entity::class))
            ->shouldBeFinal()
        ;
    }
}

Rule identifiers

When a rule is violated, the error message will contain an identifier that you can use to ignore the error.

Currently, the error identifier is based on the rule method name.

Dynamic Rule Sets

It is possible to dynamically create rules by returning an iterable of Rules from your method:

namespace App\Tests\Architecture;

use PHPat\Selector\Selector;
use PHPat\Test\Builder\Rule;
use PHPat\Test\PHPat;

final class ConfigurationTest
{
    private const DOMAINS = [
        'App\Domain1',
        'App\Domain2',
    ];

    /**
     * @return iterable<Rule>
     */
    public function test_domain_independence(): iterable
    {
        foreach(self::DOMAINS as $domain) {
            yield PHPat::rule()
                ->classes(Selector::inNamespace($domain))
                ->canOnlyDependOn()
                ->classes(Selector::inNamespace($domain));
        }
    }
}

Extending test classes

You might want to reuse rules in different tests or parametrize them for a modular scenario.

Reusing rules is possible by extending a class or using traits.

Let's see an example:

You are splitting bounded contexts, and you want the domain of each context to be independent of the others. Your rule should check that classes in the domain of a context do not depend on classes in the domain of another context.

This is what you can do:

namespace App\Tests\Architecture;

abstract class AbstractDomainTest
{
    public final function test_bounded_context_domain_independence(): Rule
    {
        return PHPat::rule()
            ->classes(
                Selector::inNamespace(sprintf('App\Module\%s\Domain', $this->getModuleName()))
            )
            ->canOnlyDependOn()
            ->classes(
                Selector::inNamespace(sprintf('App\Module\%s\Domain', $this->getModuleName())),
                Selector::inNamespace('App\Module\Shared\Domain'),
            )
            ->because('Domain should not use code from other contexts');
    }

    abstract protected function getModuleName(): string
}

namespace App\Tests\Architecture\User;

final class UserDomainTest extends AbstractDomainTest
{
    protected function getModuleName(): string
    {
        return 'User';
    }
}

Note that you would only need to register the UserDomainTest class as a PHPat test in the PHPStan config file.