Skip to content

Instantly share code, notes, and snippets.

@danscotton
Created March 26, 2014 16:15
Show Gist options
  • Save danscotton/9787026 to your computer and use it in GitHub Desktop.
Save danscotton/9787026 to your computer and use it in GitHub Desktop.

The problem with Faking it

The Problem

Developer (A) creates 2 classes, MyClass and Dependency:

class MyClass
{
    private $dependency;

    public function __construct($dependency)
    {
        $this->dependency = $dependency;
    }

    public function addNumber($number)
    {
        $this->dependency->addNumber($number);
    }

    public function getNumbers()
    {
        return $this->dependency->getListOfNumbers();
    }
}

class Dependency
{
    private $listOfNumbers = array();

    public function __construct()
    {
        $this->addNumber('one');
    }

    public function addNumber($number)
    {
        $this->listOfNumbers[] = $number;
    }

    public function getListOfNumbers()
    {
        return $this->listOfNumbers;
    }
}

Then writes a phpunit test (naughty):

class MyClassTest extends PHPUnit_Framework_TestCase
{
    public function testGetNumbers()
    {
        $mock = Phake::mock('Dependency');
        Phake::when($mock)->getListOfNumbers()->thenReturn(array('one', 'two'));

        $myClass = new MyClassOne($mock);
        $myClass->addNumber('one');
        $myClass->addNumber('two');

        $numbers = $myClass->getNumbers();

        $this->assertEquals('one', $numbers[0]); // true
    }
}

The test asserts that the given condition is true. Great :)

Now, Developer (B) decides to makes some changes to the Dependency class and throw an exception in case someone tries to override the default value:

class Dependency
{
    private $defaultNumber = 'one';
    private $listOfNumbers = array();

    public function __construct()
    {
        $this->addNumber('one');
    }

    public function addNumber($number)
    {
        if (!empty($this->listOfNumbers) && $number === $this->defaultNumber) {
            throw new InvalidArgumentException('Cannot override default number: ' . $this->defaultNumber);
        }

        $this->listOfNumbers[] = $number;
    }

    public function getListOfNumbers()
    {
        return $this->listOfNumbers;
    }
}

Since the first test is adding the number "one" twice, this should break it, right? Nope. The test still asserts that the given condition is true. Which makes the test completely unreliable.

The Solution

The solution is to inject a real object into the class you want to test.

class MyClassTest extends PHPUnit_Framework_TestCase
{
    public function testGetNumbers()
    {
        $dependency = new Dependency();

        $myClass = new MyClassOne($dependency);
        $myClass->addNumber('one'); // throws an InvalidArgumentException
        $myClass->addNumber('two');

        $numbers = $myClass->getNumbers();

        $this->assertEquals('one', $numbers[0]);
    }
}

This way you'll always find out if a change to a dependency class breaks your code or not. Testing your class in isolations doesn't mean mocking and faking all the dependencies, it means not making calls to the db, http requests and i/o operations, just to name a few.

HTH

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment