4

I was wondering whether it's possible to inject dependencies into classes derived from PHPUnit_Framework_TestCase via e.g. some context test suite - in a manner that PHPUnit could handle irregardless whether it has been invoked manually, with a phpunit.xml configuration file or else?

Please consider the following example:

<?php
interface AnyGreeter {   
    public function greet($recipient);
}   

class FriendlyGreeter implements AnyGreeter {   
    public function greet($recipient) {   
        return "Hello {$recipient}!";
    }   
}   

class DorkGreeter implements AnyGreeter {
    public function greet($recipient) {   
        return "Get lost, {$recipient}!";
    }   
}

Now I want to provide some general test for classes implementing AnyGreeter, e.g.:

<?php
class GeeterTest extends PHPUnit_Framework_TestCase {

    public function testGreet() {   
        $greeter = $this->getGreeter();
        $message = $greeter->greet("world");
        $this->assertContains("world", $message);
    }   

    public function setGreeter(AnyGreeter $greeter) {   
        $this->greeter = $greeter;
    }   

    public function getGreeter() {   
        if (null === $this->greeter) {
            $this->markTestSkipped("No greeter provided");
        }   
        return $this->greeter;
    }

    private $greeter;
} 

This may get re-used by my own as well as any future implementations (which I do not control).

How is this possible, especially in a project that relies on interfaces heavily? I don't want to write abstract tests and extend them for every single implementation --

Thanks!

4
  • Realistically, you should be creating a separate test case for every implementation. If you don't think it justifies a separate unit test, then it arguably doesn't justify a separate implementation. To test shared functionality, perhaps there should be an AbstractGreeter? Commented May 31, 2012 at 19:49
  • Yeah, that's the way it is currently. But more than half of these classes do not actually contain any additional tests.. BTW, there's indeed an AbstractGreeter also - but implementors of AnyGreeter are not forced to use it, of course. Commented May 31, 2012 at 19:52
  • 1
    If there's additional code, there should be additional tests ;-) Commented May 31, 2012 at 21:55
  • Amen. But considering a more complex pattern, like the DOMImplementation interface (the one from the W3C recommendation, not PHP), which is somehow like the "root" to access a whole bunch of other interface-implementations, it's easy to see that a more generic testing just makes sense - especially when dealing with more than one concrete implementation Commented May 31, 2012 at 22:11

2 Answers 2

1

I have never seen this done, and there's nothing in the docs. Seems a bit complex for a test. I'd solve your problem by using a data provider to pass greeter objects to the testGreeter method, then all you have to do is maintain an array of concrete greeter classes in the provider.

See the 'data provider' section here: http://www.phpunit.de/manual/3.2/en/writing-tests-for-phpunit.html

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you, I've already thought about that. The point is that AnyGreeter (as well as a bunch of other interfaces) is going to be a part of a greater library, and I believe that it should provide users with the possibility to re-use the libs tests - without actually modifying them.
I see what you mean. I think the reason that the solution is evading you is because it's contrary to the principles on which the test framework works. An interface in Php only restricts the name, scope and signature of method, therefore it is up to the implementing class to decide its functionality and return type etc. As such, a test written in advance is probably not useful.
You're right, without a doubt. Although it's very common to use the comment block above an interface or abstract method for further restrictions not explainable by the PHP syntax - like the return type or allowed exceptions. Thus; I still believe that there's a valid use-case for generic test cases that verify the interface "contract" being kept
1

I think your best approach would use providers as mentioned by Glen:

class GreeterTestCase extends PHPUnit_Framework_TestCase
{
    protected function getGreeter()
    {   
    }   

    /** 
     * @dataProvider providerGreeter
     */
    public function testGreet(AnyGreeter $greeter)
    {   
        $message = $greeter->greet('world');
        $this->assertType('string', $message, 'AnyGreeter::greet() must return string');
        $this->assertContains('world', $message);
    }   

    public function providerGreeter()
    {   
        return array(array($this->getGreeter()));
    }   
}

This way, other parts of the library can extend this test case to test their own implementation meets the basic interface spec. Any of their custom unit tests specific to that implementation go in the extended test case.

Now you have a choice of testing a single implementation (which may have its own unit tests), or just grouping together a load of implementations to get the default tests:

class AnyGreeterTestCase extends GreeterTestCase
{
    public function providerGreeter()
    {
        return array(
            array(new FriendlyGreeter()),
            array(new DorkGreeter()),
        );
    }
}

1 Comment

Well, thank you for your suggestions. Although it's not exactly what I wanted - a way to provide the greeter implementation(s) only once, in the test suite -, it may lead me to some solution.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.