Supposing I have a trait which currently has a method:
trait MyTrait
{
public function traitMethod()
{
return true;
}
}
Now let's say this trait is used by several classes but I don't want to write a unit-test for every class. Instead I want to write a single unit-test only for the trait:
public function testTraitMethod()
{
$trait = $this->getMockForTrait(MyTrait::class);
$this->assertTrue($trait->traitMethod());
}
But the problem is that a class may actually override trait's method:
class MyClass
{
use MyTrait;
public function traitMethod()
{
return false;
}
}
In this case MyClass
is doing something wrong but I'm not aware of it since I'm testing only the trait.
My idea would be write a single unit-test for every class just to check if it's using that trait and it's not overriding that method. If a class needs to override trait's method then it needs a specific unit-test as well.
Currently I'm writing unit-tests for each class that implements my trait but it's of course kind of copy-paste tests everywhere.
So is there a way to test if a class calls it's underlying trait method?
I found a solution using Reflection
, I'll post it in case someone needs it because I couldn't find anything related to my problem. Feel free to comment or add different solutions if you want.
So the following test asserts that $serviceClass
uses $traitClass
and doesn't override methods declared in $traitClass
except abstract ones and those which are manually added to $overriddenMethods
array.
public function testServiceUsesTrait()
{
$serviceClass = MyClass::class;
$traitClass = MyTrait::class;
$this->assertContains($traitClass, (new \ReflectionClass($serviceClass))->getTraitNames());
$reflectedTrait = new \ReflectionClass($traitClass);
$reflectedTraitFile = $reflectedTrait->getFileName();
/**
* If $serviceClass overrides some trait methods they
* should be inserted into this array to avoid test failure.
* Additional unit-tests should be written for overridden methods.
*/
$overriddenMethods = [];
foreach ($reflectedTrait->getMethods() as $traitMethod) {
if ($traitMethod->isAbstract() || in_array($traitMethod->getName(), $overriddenMethods, true)) {
continue;
}
$classMethod = new \ReflectionMethod($serviceClass, $traitMethod->getName());
$this->assertSame($reflectedTraitFile, $classMethod->getFileName(), sprintf(
'Trait method "%s" is overridden in class "%s" thus it must be excluded from this test.',
$traitMethod->getName(), $serviceClass
));
}
}
I also tried to compare declaring classes using $classMethod->getDeclaringClass()
instead of comparing filenames but it didn't work: even if trait method is not overridden in class, getDeclaringClass()
always returns the class itself.