Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why phpunit doesn't run __destruct() in mocked class and how to force it?

Code will explain everything:

<?php

class ATest extends PHPUnit_Framework_TestCase
{
    public function testDestructorOnOriginalClass() {
        $a = new A();                                             // It
        unset($a);                                                // works
        echo " great!";                                           // great!
        $this->expectOutputString('It works great!');
    }

    public function testDestructorOnMockedClass() {
        $a = $this->getMock('A', array('someNonExistingMethod')); // It
        unset($a);                                                // works
        echo " great!";                                           // great!
        $this->expectOutputString('It works great!');
    }
}

class A {
    public function __construct()
    {
        echo "It";
    }

    public function __destruct()
    {
        echo " works";
    }
}

and the output:

# phpunit ATest.php 
PHPUnit 3.7.13 by Sebastian Bergmann.

.F

Time: 0 seconds, Memory: 3.50Mb

There was 1 failure:

1) ATest::testDestructorOnMockedClass
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'It works great!'
+'It great! works'


FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

As you can see in the second test it prints works in wrong order, probably because phpunit stores reference to mock somewhere and __destruct() is called at the end of test... Ok I've already checked getMock() method and indeed it stores reference to mocked object ($this->mockObjects[] = $mockObject;) efficiently blocking object from being destructed and so __destructor() is never called.

/**
 * Returns a mock object for the specified class.
 *
 * @param  string  $originalClassName
 * @param  array   $methods
 * @param  array   $arguments
 * @param  string  $mockClassName
 * @param  boolean $callOriginalConstructor
 * @param  boolean $callOriginalClone
 * @param  boolean $callAutoload
 * @param  boolean $cloneArguments
 * @return PHPUnit_Framework_MockObject_MockObject
 * @throws PHPUnit_Framework_Exception
 * @since  Method available since Release 3.0.0
 */
public function getMock($originalClassName, $methods = array(), array $arguments = array(), $mockClassName = '', $callOriginalConstructor = TRUE, $callOriginalClone = TRUE, $callAutoload = TRUE, $cloneArguments = FALSE)
{
    $mockObject = PHPUnit_Framework_MockObject_Generator::getMock(
      $originalClassName,
      $methods,
      $arguments,
      $mockClassName,
      $callOriginalConstructor,
      $callOriginalClone,
      $callAutoload,
      $cloneArguments
    );

    $this->mockObjects[] = $mockObject;

    return $mockObject;
}

So the question is - is there a way to prevent this? Ignoring __destruct() when it should be called is I think bad limitation.

like image 815
Kamil Dziedzic Avatar asked Feb 13 '13 16:02

Kamil Dziedzic


People also ask

Which method is used to create a mock with PHPUnit?

PHPUnit provides methods that are used to automatically create objects that will replace the original object in our test. createMock($type) and getMockBuilder($type) methods are used to create mock object. The createMock method immediately returns a mock object of the specified type.

In what order does PHPUnit run tests?

Usually each test must not depend on any other tests, so they can run in any order.


1 Answers

The thing is when you don't pass any values to a second parameter of "getMock" method, PHPUnit will stub all methods from the class you are mocking (including "__destruct").

But if you specify at least one method (it may be even non existing method) PHPUnit will stub only these methods you pass in second argument.

So if you want keep all methods, but you want also create mock, you should do this in that way:

$mock = $this->getMock('A', array('someNonExistingMethod'));

If you change this line you test should pass.

like image 179
Cyprian Avatar answered Sep 27 '22 17:09

Cyprian