I am aware that one can add custom assertion by extending the default TestCase, e.g. I added assertions to check that an array may only contain one specific value:
<?php
namespace Kopernikus\TimrReportManager;
use PHPUnit\Framework\TestCase;
abstract class ArrayContainsValueTestCase extends TestCase
{
public static function assertArrayOnlyContainsTrue(array $haystack): void
{
static::assertArrayOnlyContainsSameValue(true, $haystack);
}
public static function assertArrayOnlyContainsSameValue(mixed $expectedValue, array $haystack): void
{
$haystack = array_unique($haystack);
static::assertTrue(static::areOnlySameValuesInArray($expectedValue, $haystack), message: 'The array contains of different values, yet sameness was expected');
}
private static function areOnlySameValuesInArray(mixed $expectedValue, array $haystack): bool
{
$haystack = array_unique($haystack);
if (count($haystack) !== 1) {
return false;
}
return reset($haystack) === $expectedValue;
}
public static function assertArrayOnlyContainsFalse(array $haystack)
{
static::assertArrayOnlyContainsSameValue(false, $haystack);
}
}
This is an assertion that is very agnostic to the domain.
Yet other custom assertions I want build are much more bound to the domain of the project, e.g. I created an assertion to check that a custom TimeEntry value object contains excatly one a specific numeric ticket id, and that one should live in its own class:
<?php
namespace Kopernikus\TimrReportManager;
use Kopernikus\TimrReportManager\Dto\TimeEntry;
use PHPUnit\Framework\TestCase;
abstract class TimeEntryTestCase extends TestCase
{
public static function assertTimeEntryHasTicketId(TimeEntry $timeEntry, int $ticketNumber)
{
$ticketNumberHashtag = '#' . (string)$ticketNumber;
$count = substr_count($timeEntry->description, '#');
static::assertSame(1, $count, 'the time entry must only contain one hashtag for the ticket id');
static::assertSame($timeEntry->ticket, $ticketNumberHashtag);
}
}
Assume I have a test class, MyTestThatRequiredBothAssertions. I could achieve that via:
ArrayContainsValueTestCase extends TestCaseTimeEntryTestCase extends ArrayContainsValueTestCaseMyTestThatRequiredBothAssertions extends TimeEntryTestCase
Yet not every actual TimeEntryTestCase would need the assertions provided by ArrayContainsValueTestCase.
I furthermore plan to create a couple of custom assertions, not only two, so the inheritance tree seems likely to get out of hand.
I would rather do:
abstract class ArrayContainsValueTestCase extends TestCaseabstract class TimeEntryTestCase extends TestCase
and would like to use them in a specific testcase like this:
class MyTestThatRequiredBothAssertions extends ArrayContainsValueTestCase, TimeEntryTestCase
and add further TestCases on demand, yet php allows only extending one class, so this won't work.
Is there another solution I am missing to separate the concerns here?
Can I provide my custom assertions differently, while retaining IDE support (auto-completion of the method names and their parameter value) of the methods within a test class?
I want to have multiple classes defining custom assertion, so putting them all in a single file is not something I want to do.
Each of those should at best only extend the default TestCase-class, yet I want to be able to mix them with one another freely.
Only if a SpecificTestCase depends on the assertions of another CustomTestCase, I am ok with them depending on one another.
I also thought about using traits, yet a trait cannot extend another class, so this:
trait MyCustomAssertion extends TestCase {
}
is also not allowed.
