phpmockingphpunitdepends

What happens to objects passed between dependent PHPUnit tests?


This isn't so much a question as an attempt to save somebody else the hour I just wasted on PHPUnit.

My problem was that my mock object, when used in a dependent test, was not returning the expected value. It seems that PHPUnit does not preserve the same object between dependent tests, even though the syntax makes it look like it does.

Does anyone know why PHPUnit does this? Is this a bug? Things like this in PHPUnit make it very frustrating to use.

<?php 
class PhpUnitTest
extends PHPUnit_Framework_TestCase
{
private $mock;

public function setUp()
{
    $this->mock = $this->getMock('stdClass', array('getFoo'));

    $this->mock->expects( $this->any() )
        ->method('getFoo')
        ->will( $this->returnValue( 'foo' ) );
}

public function testMockReturnValueTwice()
{
    $this->assertEquals('foo', $this->mock->getFoo());
    $this->assertEquals('foo', $this->mock->getFoo());

    return $this->mock;
}

/**
 * @depends testMockReturnValueTwice
 */
public function testMockReturnValueInDependentTest($mock)
{
    /* I would expect this next line to work, but it doesn't! */
    //$this->assertEquals('foo', $mock->getFoo());

    /* Instead, the $mock parameter is not the same object as
     * generated by the previous test! */
    $this->assertNull( $mock->getFoo() );
}

}

Solution

  • Mock objects in PHPUnit are attached to the test instance for which they are created, and this by definition means a single test method. The reason for this is that PHPUnit allows you to specify expectations on a mock that must be satisfied during a test. To do this it asserts those expectations once the method terminates successfully. If the mock lived across tests, expectations wouldn't work.

    The problem is that this doesn't support stub objects: mocks that contain only canned actions to be taken in response to methods and inputs. Stubs do not validate that their methods are called as full mocks can. Perhaps PHPUnit could benefit from the ability to create stubs in setUpBeforeClass() that are not tied to the test instance.

    Your other option is to use an external mock object library such as Mockery or Phake.

    Edit: After looking over your sample code again, I wonder why you are surprised by this behavior. As Shaunak wrote, setUp() is called on a new instance before each test method is executed. Thus, each instance receives a new mock stdClass. If you want only one test method to receive an expectation, add it inside the test method itself. You can still create the mock object in setUp() with any behavior that should be common to all test methods.