views:

532

answers:

6

For objects which compose another object as part of their implementation, what's the best way to write the unit test so only the principle object gets tested? Trivial example:

class myObj { 
    public function doSomethingWhichIsLogged()
    {
        // ...
        $logger = new logger('/tmp/log.txt');
        $logger->info('some message');
        // ...
    }
}

I know that the object could be designed so that the logger object dependency could be injected and hence mocked in a unit test, but that's not always the case - in more complicated scenarios, you do need to compose other objects or make calls to static methods.

As we don't want to test the logger object, only the myObj, how do we proceed? Do we create a stubbed "double" with the test script? Something like:

class logger
{
    public function __construct($filepath) {}
    public function info($message) {}
}

class TestMyObj extends PHPUnit_Framework_TestCase 
{
    // ...
}

This seems feasible for small objects but would be a pain for more complicated APIs where the SUT depended on the return values. Also, what if you want to test the calls to the dependency object in the same was you can with mock objects? Is there a way of mocking objects which are instantiated by the SUT rather than being passed in?

I've read the man page on mocks but it doesn't seem to cover this situation where the dependency is composed rather than aggregated. How do you do it?

A: 

You can provide dependency information within the doc-comment of the test:

/**
 * @depends otherTestClass::testSomethingElse
 */
public function testSomething() { ...

You can find more information following this link

soulmerge
This is the wrong kind of dependency. I'm concerned with dependencies between the objects being tested rather than dependencies between the tests themselves. Unless I'm missing something.
DavidWinterbottom
+2  A: 

As you seem to be aware already, Concrete Class Dependencies makes testing hard (or outright impossible). You need to decouple that dependency. A simple change, that doesn't break the existing API, is to default to the current behaviour, but provide a hook to override it. There are a number of ways that this could be implemented.

Some languages have tools that can inject mock classes into code, but I don't know of anything like this for PHP. In most cases, you would probably be better off refactoring your code anyway.

troelskn
It seems like you are saying that any form of aggregation is bad as it's a concrete dependency which is a hard coupling and hence hard to test. Is this right? I think I agree but wouldn't this lead to a heavy bootstrap script where all dependency objects are injected into where they are needed?
DavidWinterbottom
@troelskn, just out of curiosity, could you give an example of tools for those languages you're talking about.
Ionuț G. Stan
@david Yes to the first part, and sort-of-yes to the second part, except that "heavy" is subjective. DI containers are a way to ease the pain a bit.
troelskn
@ionut I was thinking about http://code.google.com/p/powermock/ which uses a custom class-loader.
troelskn
A: 

Looks like I misunderstood the question, let me try again:

You should use the singleton pattern or a factory for the logger, if it's not too late already:

class LoggerStub extends Logger {
    public function info() {}
}
Logger::setInstance(new LoggerStub());
...
$logger = Logger::getInstance();

If you can't change the code, you could use a catch-all class that is overloading __call()

class GenericStub {
    public function __call($functionName, $arguments) {}
}
soulmerge
+2  A: 

Following troelskn advise here's a basic example of what you should do.

<?php

class MyObj
{
    /**
     * @var LoggerInterface
     */
    protected $_logger;

    public function doSomethingWhichIsLogged()
    {
        // ...
        $this->getLogger()->info('some message');
        // ...
    }

    public function setLogger(LoggerInterface $logger)
    {
        $this->_logger = $logger;
    }

    public function getLogger()
    {
        return $this->_logger;
    }
}


class MyObjText extends PHPUnit_Framework_TestCase
{
    /**
     * @var MyObj
     */
    protected $_myObj;

    public function setUp()
    {
        $this->_myObj = new MyObj;
    }

    public function testDoSomethingWhichIsLogged()
    {
        $mockedMethods = array('info');
        $mock = $this->getMock('LoggerInterface', $mockedMethods);
        $mock->expects($this->any())
             ->method('info')
             ->will($this->returnValue(null));

        $this->_myObj->setLogger($mock);

        // do your testing
    }
}

More information about mock objects can be found in the manual.

Ionuț G. Stan
A: 

Have you looked at PHPUnit which introduced test dependencies and thus fixture reuse since 3.4? http://sebastian-bergmann.de/archives/848-Fixture-Reuse-in-PHPUnit-3.4.html

Cheers

Markus

Markus Gälli
A: 

There is actually a reasonably new extension for PHP class overloading released by the same guys that build PHPUnit. It lets you override the new operator in cases where you can't refactor the code, unfortunately it isn't that simple to install on Windows.

The URL is http://github.com/johannes/php-test-helpers/blob/master/