6

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() );
}

}
tshepang
  • 12,111
  • 21
  • 91
  • 136
Ian Phillips
  • 2,027
  • 1
  • 19
  • 31
  • Please add the commandline how you invoked phpunit as you run into the problem. -- and is there a reason you make `$mock` a private member? – hakre Jul 25 '11 at 19:56
  • 1
    AFAIK phpunit runs the setUp() method before each test, so that resets the value of $this->mock – Tom Imrei Jul 25 '11 at 20:26
  • I would have expected that this works as you written it down, i thought `setUp()` would not be called for @dependant tests and so i was really surprised that that failed too... That might have bitten me BADLY if I'd had a mock passed to a class i pass around with @depends :) – edorian Jul 26 '11 at 08:24
  • @itom99: yes, I would have expected $this->mock to be reset, but look carefully at the code: in testMockReturnValueInDependentTest(), the mock object gets passed in as a local variable. – Ian Phillips Jul 26 '11 at 16:05
  • @hakre: Are you wondering why I made $this->mock private, or why I made it an instance variable (instead of a local one)? – Ian Phillips Jul 26 '11 at 19:36
  • @Ian Phillips: I'm trying to understand what you do and for what reason you make it a private member of the testcase, a fixture and a dependent at once. – hakre Jul 27 '11 at 04:15
  • I lost 1 hour anyway, but you prevent me loosing more. Thanks! – haltabush Mar 02 '15 at 11:43

2 Answers2

5

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.

David Harkness
  • 35,992
  • 10
  • 112
  • 134
  • So to rephrase: After each tests all the mock objects that got created in that test run are stripped of all their assigned `expect`ations? – edorian Jul 26 '11 at 08:26
  • @David: The reason I'm confused is that the syntax makes it look like the return value of the dependee is passed to the depender. If PHPUnit rebuilds everything from scratch, why does it provide this misleading syntax in the first place? – Ian Phillips Jul 26 '11 at 16:09
  • @edorian - The expectations are validated, but AFAIK they aren't stripped. This happens after the test method completes successfully but before calling `tearDown()`. While you could add more expectations and calls to the mock in `tearDown()`, that wouldn't be very useful. – David Harkness Jul 26 '11 at 20:45
  • @Ian - Ah, I didn't see you were passing the mock to the next test. While some of its expectations will probably work--asserting parameter values passed to `with()` and performing actions specified in `will()`--the call count expectations will not be verified because the mock you pass to the dependent method is not attached to its test case instance. You could call `__phpunit_verify()` on the mock object, but you're treading on thin ice. See `PHPUnit_Framework_TestCase::verifyMockObjects()` for details: each mock is verified and cleaned up. Dig deeper to see what that entails. – David Harkness Jul 26 '11 at 20:51
  • 2
    @Ian - That the commented-out call in the dependent method fails tells me that `__phpunit_cleanup()` must be removing the `getFoo()` expectation. – David Harkness Jul 26 '11 at 20:52
  • @David - The first paragraph in your original answer has clued me in to the fact that I'm not clear on the difference between mocks and stubs. I'll take your good advice and "dig deeper." Thanks for your help; I clicked the big green check next to your answer, so hopefully you'll get some "points" or something. (I'm new to this StackOverflow thing.) – Ian Phillips Jul 28 '11 at 04:09
  • @Ian - Thanks :) And I reread your first comment to my answer and realized I missed it before. The return value from the dependee is definitely passed to the depender. The problem here is that the object you're passing is being altered by PHPUnit as we've discussed. If you need a very simple stub like the above, you could either recreate it or build one manually. Anything more complicated is probably a sign that the test is doing too much. – David Harkness Jul 28 '11 at 18:05
2

I am not a php guy ,so correct me if I am wrong, but the all unit tests are designed to run in following sequence,

Setup --> test function --> destroy.

so setup and destroy function is called each time before executing any test function. This is done on purpose to preserve the purpose of unit testing.

If you want to have dependent unit test cases then you have to code them, that way instead of depending on global variables to do that (this defeats the purpose of unit tests!). If there are is a test case 'A' that depends on some function, call that function from 'A' and then assert the values.

Shaunak
  • 17,377
  • 5
  • 53
  • 84