Skip to content

getMock fails on abstract class containing protected abstract method #95

Closed
ktyl opened this Issue Aug 31, 2012 · 10 comments

5 participants

@ktyl
ktyl commented Aug 31, 2012
abstract class Foo {
    abstract protected function bar();
}

class MyTest extends PHPUnit_Framework_TestCase {

    public function testTest() {
        $o = $this->getMock('Foo');
    }
}

Following code fails. I would expect that in such case getMock will mock the class and all its abstract methods.
This issue is present in PHPUnit_MockObject 1.1.0 and 1.2.0 RC2

Error message:

Fatal error: Class Mock_Foo_199d5470 contains 1 abstract method and must therefo
re be declared abstract or implement the remaining methods (Foo::bar) in D:\Deve
l\web\xampp\php\pear\PHPUnit\Framework\MockObject\Generator.php(236) : eval()'d
code on line 63
 Call Stack:
    0.0007     124800   1. {main}() D:\Devel\web\xampp\php\phpunit:0
    0.0082     377056   2. PHPUnit_TextUI_Command::main() D:\Devel\web\xampp\php
\phpunit:46
    0.0082     380280   3. PHPUnit_TextUI_Command->run() D:\Devel\web\xampp\php\
pear\PHPUnit\TextUI\Command.php:130
    0.0863    1408968   4. PHPUnit_TextUI_TestRunner->doRun() D:\Devel\web\xampp
\php\pear\PHPUnit\TextUI\Command.php:192
    0.0936    1611648   5. PHPUnit_Framework_TestSuite->run() D:\Devel\web\xampp
\php\pear\PHPUnit\TextUI\TestRunner.php:325
    0.0937    1612392   6. PHPUnit_Framework_TestSuite->runTest() D:\Devel\web\x
ampp\php\pear\PHPUnit\Framework\TestSuite.php:745
    0.0937    1612408   7. PHPUnit_Framework_TestCase->run() D:\Devel\web\xampp\
php\pear\PHPUnit\Framework\TestSuite.php:772
    0.0938    1612976   8. PHPUnit_Framework_TestResult->run() D:\Devel\web\xamp
p\php\pear\PHPUnit\Framework\TestCase.php:751
    0.1004    1651600   9. PHPUnit_Framework_TestCase->runBare() D:\Devel\web\xa
mpp\php\pear\PHPUnit\Framework\TestResult.php:649
    0.1029    1734968  10. PHPUnit_Framework_TestCase->runTest() D:\Devel\web\xa
mpp\php\pear\PHPUnit\Framework\TestCase.php:804
    0.1030    1735656  11. ReflectionMethod->invokeArgs() D:\Devel\web\xampp\php
\pear\PHPUnit\Framework\TestCase.php:942
    0.1030    1735672  12. MyTest->testTest() D:\Devel\web\xampp\php\pear\PHPUni
t\Framework\TestCase.php:942
    0.1030    1735736  13. PHPUnit_Framework_TestCase->getMock() D:\Devel\phq\so
urces\flexapi\tests\lib\itDataSource\MyTest.php:10
    0.1056    1825456  14. PHPUnit_Framework_MockObject_Generator::getMock() D:\
Devel\web\xampp\php\pear\PHPUnit\Framework\TestCase.php:1270
    0.1091    1838552  15. PHPUnit_Framework_MockObject_Generator::getObject() D
:\Devel\web\xampp\php\pear\PHPUnit\Framework\MockObject\Generator.php:221
    0.1093    1853216  16. eval('class Mock_Foo_199d5470 extends Foo implements
PHPUnit_Framework_MockObject_MockObject
{
    protected static $staticInvocationMocker;
    protected $invocationMocker;
    protected $id;

    public function __clone()
    {
        $this->invocationMocker = clone $this->__phpunit_getInvocationMocker();
    }

    public function expects(PHPUnit_Framework_MockObject_Matcher_Invocation $mat
cher)
    {
        return $this->__phpunit_getInvocationMocker()->expects($matcher);
    }

    public static function staticExpects(PHPUnit_Framework_MockObject_Matcher_In
vocation $matcher)
    {
        return self::__phpunit_getStaticInvocationMocker()->expects($matcher);
    }

    public function __phpunit_getInvocationMocker()
    {
        if ($this->invocationMocker === NULL) {
            $this->invocationMocker = new PHPUnit_Framework_MockObject_Invocatio
nMocker;
        }

        return $this->invocationMocker;
    }

    public static function __phpunit_getStaticInvocationMocker()
    {
        if (self::$staticInvocationMocker === NULL) {
            self::$staticInvocationMocker = new PHPUnit_Framework_MockObject_Inv
ocationMocker;
        }

        return self::$staticInvocationMocker;
    }

    public function __phpunit_hasMatchers()
    {
        return self::__phpunit_getStaticInvocationMocker()->hasMatchers() ||
               $this->__phpunit_getInvocationMocker()->hasMatchers();
    }

    public function __phpunit_verify()
    {
        self::__phpunit_getStaticInvocationMocker()->verify();
        $this->__phpunit_getInvocationMocker()->verify();
    }

    public function __phpunit_cleanup()
    {
        self::$staticInvocationMocker = NULL;
        $this->invocationMocker       = NULL;
        $this->id                     = NULL;
    }

    public function __phpunit_setId($id)
    {
        $this->id = $id;
    }
  }
') D:\Devel\web\xampp\php\pear\PHPUnit\Framework\MockObject\Generator.php:236
@ktyl ktyl closed this Aug 31, 2012
@ktyl ktyl reopened this Aug 31, 2012
@ktyl ktyl closed this Aug 31, 2012
@ktyl ktyl reopened this Aug 31, 2012
@edorian
Collaborator
edorian commented Sep 5, 2012

Thats a case where only getMockForAbstractClass() works as it was made it handle those cases.

Sadly the error message is not really pretty or clear on what is happing but the functionality it's self doesn't make a lot of sense for the current implementation of getMock() (i guess.. maybe)

@ktyl
ktyl commented Sep 5, 2012

The problem is that getMockForAbstractClass does not override non abstract methods, therefore it is not possible to set the expectations on them. Also, I would like the mocks I'm using to be "harmless" to the environment they're used in, and I cannot assure that when leaving original implementation of methods.

@edorian
Collaborator
edorian commented Sep 5, 2012
$o = $this->getMock('Foo', array());

will work then (as it tells phpunit to just mock all methods) but it's not really pretty to have to explicitly state something not really readable.

If i (or someone) can come up with a BC free way of fixing this (or just improving the error message) that can be fixed.

@ktyl
ktyl commented Sep 5, 2012

Sorry, it does not work: $o = $this->getMock('Foo', array()); is equivalent to $o = $this->getMock('Foo'); and crashes exactly in the same way.

abstract class Foo {
    protected abstract function bar();
}

class phpunitTest extends PHPUnit_Framework_TestCase {

        public function testFooBar() {
                $this->getMock('Foo', array());
        }

}

Execution:

ktyl@dev:~/devel/htdocs/playground$ phpunit phpunitTest.php
PHPUnit 3.6.12 by Sebastian Bergmann.

PHP Fatal error:  Class Mock_Foo_c4323444 contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Foo::bar) in /usr/share/php/PHPUnit/Framework/MockObject/Generator.php(218) : eval()'d code on line 56
@edorian
Collaborator
edorian commented Sep 6, 2012

Sorry. My bad. Named the file wrong and the test didn't execute without me noticing.

@whatthejeff
Collaborator

Here's what I would do:

<?php

abstract class Foo
{
    protected abstract function bar();
    public function foobar() {
        return TRUE;
    }
}

class phpunitTest extends PHPUnit_Framework_TestCase
{
    public function testFooBar()
    {
        $methods = get_class_methods('Foo');
        $mock = $this->getMockBuilder('Foo')
                     ->setMethods($methods)
                     ->getMockForAbstractClass();

        $mock->expects($this->once())
             ->method('foobar')
             ->will($this->returnValue(FALSE));

        $this->assertFalse($mock->foobar());
    }
}
@edorian
Collaborator
edorian commented Jan 14, 2013

Agreed. Even so I think it makes sense to leave the issue open a it should work

@joetravis

So I'm new to this, but the following tests pass, which does appear to address the issue of mocking concrete methods on abstract classes:

<?php

abstract class AbstractMockTestClass
{
    abstract public function doSomething();

    public function doSomethingConcrete() {
        return TRUE;
    }
}

class phpunitTest extends PHPUnit_Framework_TestCase
{
    public function testGetMockForAbstractClassMockingConcreteMethod()
    {
        $mock = $this->generator->getMockForAbstractClass('AbstractMockTestClass', array(), '', TRUE, TRUE, TRUE, array('doSomethingConcrete'));
        $mock->expects($this->any())
            ->method('doSomethingConcrete')
            ->will($this->returnValue('Concrete'));

        $this->assertEquals('Concrete', $mock->doSomethingConcrete());
    }

    public function testConcreteFunctionsCanBeMockedOnAbstractClasses() {
        $spec = $this->getMockBuilder('AbstractMockTestClass');
        $spec->setMethods(array('doSomethingConcrete'));
        $mock = $spec->getMockForAbstractClass();

        $mock->expects($this->any())
            ->method('doSomethingConcrete')
            ->will($this->returnValue('Concrete'));

        $this->assertEquals('Concrete', $mock->doSomethingConcrete());
    }
}

Does this meet the need described above? The doc block for getMockForAbstractClass describes this, but it needs to be updated, since it is referencing the wrong parameter order.

Is this bug open so that the error message can be made nicer, or so getMock can provide the functionality that is available in getMockForAbstractClass? Or am I missing something?

@joetravis

OK. I was missing something. When in doubt, RTFM... Default behavior for getMock($className) with no methods passed should provide configurable dummy implementations for ALL methods.

@sebastianbergmann
Owner

Dear contributor,

let me start by apologizing for not commenting and/or working on the issue you have reported or merging the pull request you have sent sooner.

PHPUnit 5.0 was released today. And today I am closing all open bug reports and pull requests for PHPUnit and its dependencies that I maintain. Please do not interpret the closing of this ticket as an insult or a lack of interest in your problem. I am sorry for any inconvenience this may cause.

If the topic of this ticket is still relevant then please open a new ticket or send a new pull request. If your ticket or pull request is about a defect then please check whether the issue still exists in PHPUnit 4.8 (which will received bug fixes until August 2016). If your ticket or pull request is about a new feature then please port your patch PHPUnit 5.0 before sending a new pull request.

I hope that today's extreme backlog grooming will allow me to respond to bug reports and pull requests in a more timely manner in the future.

Thank you for your understanding,
Sebastian

@sebastianbergmann sebastianbergmann locked and limited conversation to collaborators Oct 2, 2015
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Something went wrong with that request. Please try again.