- 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 TestCase
Configured 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 TestCaseBase
Configured 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 TestCase
Configured in test provider as
yield 'Every test is final' => [
new EveryTestIsFinal($testFiles),
];