- 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, eg. create a test case like in the following example:
<?php
use Cdn77\TestUtils\TestCheck\TestCheck;
use PHPUnit\Framework\TestCase;
/**
* @group integration
* @testedClass none
*/
final class SuiteComplianceTest extends TestCaseBase
{
/** @dataProvider providerChecks */
public function testChecks(TestCheck $check) : void
{
$check->run($this);
}
/** @return Generator<string, array{callable(self): TestCheck}> */
public 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
annotation
❌
final class FooTest extends TestCase
✔️
/** @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 @testedClass
annotation to link test with tested class
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;
/** @testedClass \Ns\Unit */
final class UnitTest extends TestCase {}
Configured in test provider as
yield 'Every test has same namespace as tested class' => [
new EveryTestHasSameNamespaceAsTestedClass($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),
];