cdn77/test-utils

Cdn77 Test Utils for PHP

0.5.2 2024-08-29 07:55 UTC

README

GitHub Actions Code Coverage Downloads Packagist Infection MSI

Contents

Installation

  • Require this project as composer dev dependency:
composer require --dev cdn77/test-utils

Features

Stub

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

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),
        ];
        
        ...
    }
}

Every test has group

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),
];

Every test has same namespace as covered class

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),
];

Every test inherits from testcase base class

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
    ),
];

Every test is final

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),
];