I am not sure if this is a valid question. It is difficult when you have a question but do not know related terms to type into search. So my searches are not yielding any meaningful result. I just want to know if it is a thing to write a unit test that classes that implement a contract should pass.
For example I want to define the following contract:
abstract class FooContract
{
public function setup(): bool
{
// General setup code
}
abstract function run(): string;
}
A programmer can write a class named Baz which implements the contract thus:
class Baz extends FooContract
{
public function run(): string
{
// run code
}
}
I want to give the programmer a choice to not have to write a full test every time she implements FooContract. So I want to give the following set of unit tests to the programmer of the class Baz:
trait FooContractTest
{
private function getInstanceOrFail()
{
if (method_exists($this, 'getInstance')) {
if (($instance = $this->getInstance()) instanceof FooContract) {
return $instance;
}
}
throw new Exception('Define the method getInstance() in your test. You should then return an instance of FooContract under test from getInstance');
}
public function test_can_setup()
{
$this->assertTrue($this->getInstanceOrFail()->setup());
}
public function test_can_run()
{
$this->assertEquals('Done', $this->getInstanceOrFail()->run());
}
}
I want the programmer of Baz to run the test as follows:
class BazTest extends PHPUnit_TestCase
{
use FooContractTest;
public function getInstance()
{
return new Baz;
}
}
My question is, is this a thing? Is there a better way to do it?
CodePudding user response:
It looks to me like you want to provide a set of reusable tests. A trait containing common tests seems like a fair solution to me.
Rename the trait to make it more clear what its purpose is e.g. CommonFooContractTests
, FooContractCommonTests
. Remember, naming it with a postfix "Test" may cause problems in PHPUnit because it may think it's a test. Using words like "Common" and "Tests" makes it a bit more clear.
I would make the instance dependency a requirement in the trait by using an abstract method with an expected return type and rename the instance getter to be trait specific factory method.
trait FooContractCommonTests
{
abstract public function createFooContract(): FooContract;
public function testCanSetup(): void
{
$this->assertTrue($this->createFooContract()->setup());
}
}
If you can't use a return a type, then use a test to evaluate the expected type and make all other tests depend on it:
trait FooContractCommonTests
{
abstract public function createFooContract();
public function testGetInstanceIsInstanceOfFooContract(): void
{
$this->assertInstanceOf(FooContract::class, $this->createFooContract());
}
/**
* @depends testGetInstanceIsInstanceOfFooContract
*/
public function testCanSetup(): void
{
$this->assertTrue($this->createFooContract()->setup());
}
}
Other than that I don't see much wrong with using a common tests trait, even with the way you already implemented it using getInstanceOrFail
, it's not so bad, though I do think the abstract method with expected return type is the cleanest and most clear implementation.