- Require this project as composer dev dependency:
composer require --dev cdn77/test-utils
Factory to create object through Reflection in order to bypass the constructor.
<?phpclass MyEntity {
/** @var string */private$property1;
/** @var string */private$property2;
publicfunction__construct(string$property1, string$property2) {
$this->property1 = $property1;
$this->property2 = $property2;
}
publicfunctionsalute() : string {
returnsprintf('Hello %s!', $this->property2);
}
}When testing method salute(), you only need the tested class to have property2 set, you don't want to worry about property1.
Therefore in your test you can initialize MyEntity using Stub::create() like this:
$myEntity = Stub::create(MyEntity::class, ['property2' => 'world']);
self::assertSame('Hello world!', $myEntity->salute());It comes handy when class constructor has more arguments and most of them are not required for your test.
It is possible to extend stubs:
$myEntity = Stub::create(MyEntity::class, ['property2' => 'world']);
$myEntity = Stub::extends($myEntity, ['property1' => 'value']);
// property 1 and 2 are set nowself::assertSame('Hello world!', $myEntity->salute());Test Checks are used to assert that tests comply with your suite's standards (are final, extend correct TestCaseBase etc.)
To run them, e.g. create a test case like in the following example:
<?phpuseCdn77\TestUtils\TestCheck\TestCheck;
usePHPUnit\Framework\TestCase;
usePHPUnit\Framework\Attributes\CoversNothing;
usePHPUnit\Framework\Attributes\Group;
#[CoversNothing]
#[Group('integration')]
finalclass SuiteComplianceTest extends TestCaseBase
{
/** @dataProvider providerChecks */publicfunctiontestChecks(TestCheck$check) : void
{
$check->run($this);
}
/** @return Generator<string, array{callable(self): TestCheck}> */publicstaticfunctionproviderChecks() : Generator
{
$testDir = ROOT_PROJECT_DIR . '/tests';
$testFilePathNames = \Symfony\Component\Finder\Finder::create()
->in($testDir)
->files()
->name('*Test.php');
yield'Every test has group' => [
newEveryTestHasGroup($testFilePathNames),
];
...
}
}Asserts that all tests have a #[Group('x')] attribute
❌
finalclass FooTest extends TestCase✔️
usePHPUnit\Framework\Attributes\Group;
#[Group('unit')]
finalclass FooTest extends TestCaseConfigured in test provider as
yield'Every test has group' => [
newEveryTestHasGroup($testFiles),
];Asserts that all test share same namespace with class they're testing.
Consider src namespace Ns and test namespace Ns/Tests then for test Ns/Tests/UnitTest must exist class Ns/Unit.
You can use #[CoversClass] attribute to link test with tested class.
Use #[CoversNothing] attribute to skip this check.
Don't forget to enable requireCoverageMetadata="true" in phpunit config file.
namespaceNs;
finalclass Unit {} ❌
namespaceNs\Tests;
finalclass NonexistentUnitTest extends TestCase {}namespaceNs\Tests\Sub;
finalclass UnitTest extends TestCase {}✔️
namespaceNs\Tests;
finalclass UnitTest extends TestCase {}namespaceNs\Tests\Sub;
usePHPUnit\Framework\Attributes\CoversClass;
#[CoversClass('\Ns\Unit')]
finalclass UnitTest extends TestCase {}Configured in test provider as
yield'Every test has same namespace as tested class' => [
newEveryTestHasSameNamespaceAsCoveredClass($testFiles),
];Consider you have a base for all tests and want each of them extend it.
abstractclass TestCaseBase extends \PHPUnit\Framework\TestCase {}❌
finalclass FooTest extends \PHPUnit\Framework\TestCase✔️
finalclass FooTest extends TestCaseBaseConfigured in test provider as
yield'Every test inherits from TestCase Base Class' => [
newEveryTestInheritsFromTestCaseBaseClass(
$testFiles,
TestCaseBase::class
),
];Asserts all tests are final so they cannot be extended
❌
class FooTest extends TestCase✔️
finalclass FooTest extends TestCaseConfigured in test provider as
yield'Every test is final' => [
newEveryTestIsFinal($testFiles),
];