Home > Mobile >  How to write a unit test that classes implementing a contract should pass
How to write a unit test that classes implementing a contract should pass

Time:08-24

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.

  • Related