- 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.
<?php
class MyEntity
{
/** @var string */
private $property1;
/** @var string */
private $property2;
public function __construct(string $property1, string $property2)
{
$this->property1 = $property1;
$this->property2 = $property2;
}
public function salute() : string
{
return sprintf('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 now
self::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:
<?php
use Cdn77\TestUtils\TestCheck\TestCheck;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversNothing;
use PHPUnit\Framework\Attributes\Group;
#[CoversNothing]
#[Group('integration')]
final class SuiteComplianceTest extends TestCaseBase
{
/** @dataProvider providerChecks */
public function testChecks(TestCheck $check) : void
{
$check->run($this);
}
/** @return Generator<string, array{callable(self): TestCheck}> */
public static function providerChecks() : Generator
{
$testDir = ROOT_PROJECT_DIR . '/tests';
$testFilePathNames = \Symfony\Component\Finder\Finder::create()
->in($testDir)
->files()
->name('*Test.php');
yield 'Every test has group' => [
new EveryTestHasGroup($testFilePathNames),
];
...
}
}Asserts that all tests have a #[Group('x')] attribute
❌
final class FooTest extends TestCase✔️
use PHPUnit\Framework\Attributes\Group;
#[Group('unit')]
final class FooTest extends TestCaseConfigured in test provider as
yield 'Every test has group' => [
new EveryTestHasGroup($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.
namespace Ns;
final class Unit {} ❌
namespace Ns\Tests;
final class NonexistentUnitTest extends TestCase {}namespace Ns\Tests\Sub;
final class UnitTest extends TestCase {}✔️
namespace Ns\Tests;
final class UnitTest extends TestCase {}namespace Ns\Tests\Sub;
use PHPUnit\Framework\Attributes\CoversClass;
#[CoversClass('\Ns\Unit')]
final class UnitTest extends TestCase {}Configured in test provider as
yield 'Every test has same namespace as tested class' => [
new EveryTestHasSameNamespaceAsCoveredClass($testFiles),
];Consider you have a base for all tests and want each of them extend it.
abstract class TestCaseBase extends \PHPUnit\Framework\TestCase {}❌
final class FooTest extends \PHPUnit\Framework\TestCase✔️
final class FooTest extends TestCaseBaseConfigured in test provider as
yield 'Every test inherits from TestCase Base Class' => [
new EveryTestInheritsFromTestCaseBaseClass(
$testFiles,
TestCaseBase::class
),
];Asserts all tests are final so they cannot be extended
❌
class FooTest extends TestCase✔️
final class FooTest extends TestCaseConfigured in test provider as
yield 'Every test is final' => [
new EveryTestIsFinal($testFiles),
];