PHPUnit + Mock

Discussion of testing theory and practice, including methodologies (such as TDD, BDD, DDD, Agile, XP) and software - anything to do with testing goes here. (Formerly "The Testing Side of Development")

Moderator: General Moderators

PHPUnit + Mock

Postby jjrumi » Fri May 09, 2008 4:41 am

Hi all,

I'm trying to test a controller and I want to isolate it from the models it uses.
For doing so, I want to mock my Factory class (the one that instantiates all the models) and some models.
The example below mocks only one model and tries to return it everytime the Factory is called to instantiate it.

Syntax: [ Download ] [ Hide ]
 
...
// Mocked model.
$mockUsersEditprofileModel = $this->getMock( 'UsersEditprofileModel', array( 'checkCurrentEmailPass' ) );
        $mockUsersEditprofileModel->expects( $this->any() )
                                     ->method( 'checkCurrentEmailPass' )
                                     ->will( $this->returnValue( true ) );
 
// Mocked Factory object
// Here is where I get the problems.
$supFactory = $this->getMock( 'SuperFactory', array( 'getClass' ), array( $this->config ) );
$supFactory->expects( $this->any() )
                   ->method( 'getClass' )->with( $this->equalTo('UsersEditprofileModel') )
                   ->will( $this->returnValue( $mockUsersEditprofileModel ) );
 
$this->obj = new UsersCancelationController();
$this->obj->setInstance();
$this->obj->setFactory( $supFactory ); // Here I replace the current Factory for the mocked one.
 
...
 


When I execute this, I receive the following error:
Fail: "testRightObjectCreated" -> Expectation failed for method name is equal to when invoked zero or more times. Mocked method does not exist.

I think the problem comes when I use "->with( $this->equalTo('UsersEditprofileModel') )" in the $supFactory. There's no error when I skip the with part and I leave it with expects()->method()->will()... but of course it doesn't work for me because I want the Factory class to instantiate many models, not always the one I've mocked.

Any suggestions?
:)
Last edited by jjrumi on Fri May 09, 2008 11:53 am, edited 1 time in total.
jjrumi
Forum Newbie
 
Posts: 14
Joined: Tue Feb 21, 2006 11:37 am

Re: PHPUnit + Mock

Postby Weirdan » Fri May 09, 2008 7:05 am

jjrumi wrote:
When I execute this, I receive the following error:
Fail: "testRightObjectCreated" -> Expectation failed for method name is equal to when invoked zero or more times. Mocked method does not exist.

I think the problem comes when I use "->with( $this->equalTo('UsersEditprofileModel') )" in the $supFactory. There's no error when I skip the with part and I leave it with expects()->method()->will()... but of course it doesn't work for me because I want the Factory class to instantiate many models, not always the one I've mocked.

Any suggestions?
:)

'Mocked method does not exist' suggest that you're trying to express expectations on the method you didn't mock (for your Model, you mocked 'cancelAccount' but expectation are set on 'checkCurrentEmailPath'). It's weird, though, that you do not receive this error when omitting ->with() part in factory expectations
User avatar
Weirdan
Moderator
 
Posts: 5864
Joined: Mon Nov 03, 2003 7:13 pm
Location: Odessa, Ukraine

Re: PHPUnit + Mock

Postby jjrumi » Fri May 09, 2008 11:58 am

'Mocked method does not exist' suggest that you're trying to express expectations on the method you didn't mock (for your Model, you mocked 'cancelAccount' but expectation are set on 'checkCurrentEmailPath'). It's weird, though, that you do not receive this error when omitting ->with() part in factory expectations


Thanks for answering Weirdan.

Damn it! I copied it wrong! :banghead:
The "mocking" of the Model is fine, I have no problems with it. It's with the Factory where I get the problems. (I've just edited the post).

So, as I said, is with the Factory that I have problems. Whenever I try to mix $mock->expects()->method()->with()->will()
I don't even know if what I'm trying to do is possible... but seems natural to me.
jjrumi
Forum Newbie
 
Posts: 14
Joined: Tue Feb 21, 2006 11:37 am

Re: PHPUnit + Mock

Postby Weirdan » Fri May 09, 2008 3:47 pm

Syntax: [ Download ] [ Hide ]
   
<?php
class TestFactory
{
    public function getImplementation($className)
    {
    }
}
 
class TestTest extends PHPUnit_Framework_TestCase
{
    public function testSameDistinguishTwoObjects()
    {
        $obj = new stdClass;
        $obj2 = new stdClass;
        $this->assertNotSame($obj, $obj2);
    }
 
    public function testWithIsSupportedWithWill()
    {
        $factory = $this->getMock('TestFactory');
        $obj = new stdClass;
        $factory->expects($this->any())
            ->method('getImplementation')
            ->with('TestObj')
            ->will($this->returnValue($obj));
        $this->assertSame($obj, $factory->getImplementation('TestObj'));
    }
 
    public function testWithActsRegardlessOfTheCallSequence()
    {
        $factory = $this->getMock('TestFactory');
        $obj = new stdClass;
        $obj2 = new stdClass;
        $factory->expects($this->any())
            ->method('getImplementation')
            ->with('obj1')
            ->will($this->returnValue($obj));
        $factory->expects($this->any())
            ->method('getImplementation')
            ->with('obj2')
            ->will($this->returnValue($obj2));
        $this->assertSame($obj, $factory->getImplementation('obj1'));
        $this->assertSame($obj2, $factory->getImplementation('obj2'));
        $this->assertSame($obj, $factory->getImplementation('obj1'));
    }
}
?>
 


Here, only third case fails:

Syntax: [ Download ] [ Hide ]
   
TestTest
PHP Parse error:    syntax error, unexpected T_STRING, expecting T_OLD_FUNCTION or T_FUNCTION or T_VAR or '}' in /home/weirdan/
public_html/enm/build/q.php on line 4
PHPUnit 3.1.0beta4 by Sebastian Bergmann.
   
..F
   
Time: 0 seconds
   
There was 1 failure:
   
1) testWithActsRegardlessOfTheCallSequence(TestTest)
Failed asserting that two strings are equal.
expected string <obj2>
difference          <   x>
got string          <obj1>
/home/weirdan/public_html/enm/build/q.php:43
   
FAILURES!
Tests: 3, Failures: 1.
 

As you can see, latter expectation overridden the former - thus it's not what you expected. It appears what you need is not supported by PHPUnit directly - the closest you can get without extending PHPUnit is onConsecutiveCalls:
Syntax: [ Download ] [ Hide ]
   
<?php
    public function testOnConsecutiveCallsCanBeUsedToGenerateDifferentReturnValues()
    {
        $factory = $this->getMock('TestFactory');
        $obj = new stdClass;
        $obj2 = new stdClass;
        $factory->expects($this->any())
            ->method('getImplementation')
            ->will($this->onConsecutiveCalls($obj, $obj2, $obj));
        $this->assertSame($obj, $factory->getImplementation('obj1'));
        $this->assertSame($obj2, $factory->getImplementation('obj2'));
        $this->assertSame($obj, $factory->getImplementation('obj1'));
    }
?>
 


Though PHPUnit is quite easy to extend with functionality you need:
Syntax: [ Download ] [ Hide ]
   
<?php
class PHPUnit_Framework_MockObject_Stub_ParameterBasedAction implements PHPUnit_Framework_MockObject_Stub
{
    private $parameters = array();
    private $returns = array();
    private $currentParameters = null;
   
    public function with()
    {
        if (!is_null($this->currentParameters)) {
            throw new Exception('Out of sequence call - expecting ->will()');
        }
 
        $args = func_get_args();
        $this->currentParameters = new PHPUnit_Framework_MockObject_Matcher_Parameters($args);
        return $this;
    }
   
    public function will($stub)
    {
        if (is_null($this->currentParameters)) {
            throw new Exception('Out of sequence call - expecting ->with()');
        }
        if (!($stub instanceof PHPUnit_Framework_MockObject_Stub)) {
            $stub = new PHPUnit_Framework_MockObject_Stub_Return($stub);
        }
        $this->parameters[] = $this->currentParameters;
        $this->returns[] = $stub;
        $this->currentParameters = null;
        return $this;
    }
 
    public function willBe($return)
    {
        return $this->will($return);
    }
 
    public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation)
    {
        foreach ($this->parameters as $i => $matcher) {
            if ($matcher->matches($invocation)) {
                return $this->returns[$i]->invoke($invocation);
            }
        }
        throw new Exception(
            'No matching parameters found for this invocation: '
            . PHPUnit_Util_Type::toString($invocation)
        );
    }
   
    public function toString()
    {
        return 'not implemented yet';
    }
}
   
class TestFactory
{
    public function getImplementation($className)
    {
    }
}
   
class TestTest extends PHPUnit_Framework_TestCase
{
    public function testParameterBasedAction()
    {
        $factory = $this->getMock('TestFactory');
        $obj = new stdClass;
        $obj2 = new stdClass;
        $factory->expects($this->any())
            ->method('getImplementation')
            ->will(
                $this->whenCalled()
                ->with('obj1')->willBe($obj)
                ->with('obj2')->will($this->returnValue($obj2))
                ->with('qq')->will($this->throwException(new Exception('test exception')))
            );
        $this->assertSame($obj, $factory->getImplementation('obj1'));
        $this->assertSame($obj2, $factory->getImplementation('obj2'));
        $this->assertSame($obj, $factory->getImplementation('obj1'));
        try {
            $factory->getImplementation('qq');
            $this->fail('Should have thrown exception');
        } catch (Exception $e) {
            // passes
        }
    }
    public function whenCalled()
    {
        return new PHPUnit_Framework_MockObject_Stub_ParameterBasedAction;
    }
}
?>
 
Last edited by Weirdan on Fri May 09, 2008 8:38 pm, edited 3 times in total.
Reason: formatting
User avatar
Weirdan
Moderator
 
Posts: 5864
Joined: Mon Nov 03, 2003 7:13 pm
Location: Odessa, Ukraine

Re: PHPUnit + Mock

Postby jjrumi » Tue May 13, 2008 4:59 am

:)

Thanks for the answer, dude. (And sorry for the delay, we had a long weekend in Spain ;))
It has helped me to understand more the structure of PHPUnit.

With your solution is possible to mock the factory class method FOR THE SPECIFIED PARAMS but it doesn't work for calls like:
Syntax: [ Download ] [ Hide ]
 
$factory->getImplementation('obj23')
 

where obj23 is an object not declared as an expected parameter. The error I receive is this:
Fail: "testFormEntrance" -> Parameter 0 for invocation SuperFactory::getClass() does not match expected value.


The only solution I've found so far is make something like this:
Syntax: [ Download ] [ Hide ]
 
class SuperFactoryWithMocking extends SuperFactory
{
    public static $mocking = array();
 
    public function getClass( $classType )
    {
        if( !array_key_exists( $classType, SuperFactoryWithMocking::$mocking ) )
        {
            return parent::getClass( $classType );
        }
        else
        {
            return SuperFactoryWithMocking::$mocking[$classType];
        }
    }
}
 

and add in the array of the extended class all the models I want to mockin the test, although it's not my best option since I expected the Framework of PHPUnit to do it for me.
jjrumi
Forum Newbie
 
Posts: 14
Joined: Tue Feb 21, 2006 11:37 am

Re: PHPUnit + Mock

Postby jjrumi » Tue May 13, 2008 8:24 am

Uffff, PHPUnit has just thrown me a low punch...
After mocking a class like this:
Syntax: [ Download ] [ Hide ]
 
class RegistrationModel extends Model
{
    const CANCEL_SEED_KEY     = "pimpampum";
    ...
}

and trying to use the constant with RegistrationModel::CANCEL_SEED_KEY I receive the following error:
Fatal error: Undefined class constant 'CANCEL_SEED_KEY' in ...

:banghead: :banghead: :banghead:

The mocking process doesn't take into account the constants defined in the class!!!
Doing this in the tested code:
Syntax: [ Download ] [ Hide ]
 
$test = new ReflectionClass( 'RegistrationModel ' );
var_dump( $test->getConstants() );
 

prompts that there are no constants in the class :_____(
jjrumi
Forum Newbie
 
Posts: 14
Joined: Tue Feb 21, 2006 11:37 am

Re: PHPUnit + Mock

Postby Chris Corbyn » Tue May 13, 2008 9:12 am

jjrumi wrote:The mocking process doesn't take into account the constants defined in the class!!!
Doing this in the tested code:
Syntax: [ Download ] [ Hide ]
 
$test = new ReflectionClass( 'RegistrationModel ' );
var_dump( $test->getConstants() );
 

prompts that there are no constants in the class :_____(


I don't believe it should. If it mocked class constants then your production code would need to be aware of the generated mock class. Where is the code which tries to access the constant and why is it trying to access a constant for a generated mock class name? That said, I'd have thought PHPUnit would subclass the original to produce a mock with the correct type.
User avatar
Chris Corbyn
Breakbeat Nuttzer
 
Posts: 13098
Joined: Wed Mar 24, 2004 8:57 am
Location: Melbourne, Australia

Re: PHPUnit + Mock

Postby jjrumi » Tue May 13, 2008 10:04 am

Let's seeee... I may have misunderstood some concept of the whole testing process.

I have something like this (simplifying...):

Syntax: [ Download ] [ Hide ]
 
/**
 * Extending SuperFactory class to return Mocked Models.
 * SuperFactory is the class used to instantiate classes.
 */

class SuperFactoryWithMocking extends SuperFactory
{
    // Array of classes I want to be mocked.
    public static $mocking = array();
 
    /**
    * Override the getClass() function.
    * Returns an instance of the class requested. The mocked object if it's in the array.
    */

    public function getClass( $classType )
    {
        if( !array_key_exists( $classType, SuperFactoryWithMocking::$mocking ) )
        {
            return parent::getClass( $classType );
        }
        else
        {
            return SuperFactoryWithMocking::$mocking[$classType];
        }
    }
}
 
/**
 * Unit Testing for UsersCancelationController
 */

class testUsersCancelationControllerTest extends PHPUnit_Framework_TestCase
{
    public function setUp()
    {
        // Set up of the Mocked Model.
        $mockRegistrationCancelationModel = $this->getMock( 'RegistrationModel', array( 'cancelUserRegistration' ) );
        $mockRegistrationCancelationModel->expects( $this->any() )
                                         ->method( 'cancelUserRegistration' )
                                         ->will( $this->returnValue( true ) );
       
        SuperFactoryWithMocking::$mocking['RegistrationCancelationModel'] = $mockRegistrationCancelationModel;
 
        $this->obj = new UsersCancelationController();
        // Substitution of the Factory class in the Controller so it uses the extended here.
        $this->obj->setFactory( new SuperFactoryWithMocking( ) );
    }
 
    public function testLetsTestSomething()
    {
        $this->params = array();
        $this->obj->setParams( $this->params );
        $returnedValue = $this->obj->build();
 
        $this->assertTrue( $returnedValue, 'Oooooh! It has returned false!' );
    }
}
 
/**
* The tested class !!!
*/

class UsersCancelationController extends Controller
{
    // This code is executed when the test is performed.
    public function build()
    {
        ...
        // Instantiation of the Model mocked.
        $user = $this->getClass( 'RegistrationModel' );
        ...
        $user->cancelUserRegistration(); //Works fine, returns an straight TRUE as I have specified in the expected return value of the mocking object.
        ...
        echo RegistrationModel::CANCEL_SEED_KEY; // This produces the Fatal Error "Undefined class constant 'CANCEL_SEED_KEY' in...".
        ...
        return $whatever;
    }
}
 
/**
* One of the models used by the tested Controller.
*/

class RegistrationModel extends Model
{
    const CANCEL_SEED_KEY     = "pimpampum";    
    ...
    public function cancelUserRegistration()
    {
        // Queries the Database...
    }
}
 


Was this clearer, Chris?
jjrumi
Forum Newbie
 
Posts: 14
Joined: Tue Feb 21, 2006 11:37 am

Re: PHPUnit + Mock

Postby Weirdan » Sat May 31, 2008 6:36 pm

Chris Corbyn wrote:That said, I'd have thought PHPUnit would subclass the original to produce a mock with the correct type.

It does.
jjrumi wrote:Was this clearer, Chris?

Not really. It doesn't even seem to do anything with mocks at all. According to the code you've posted you're accessing constant of a class (not mock) defined in the same file as your controller. PHPUnit shouldn't (can't) affect calls like that.

What does
Syntax: [ Download ] [ Hide ]
 
var_dump(in_array('RegistrationModel', get_declared_classes()));
 

print when placed where 'echo RegistrationModel::CANCEL_SEED_KEY' is now.

The mocking process doesn't take into account the constants defined in the class!!!
Doing this in the tested code:
Syntax: [ Download ] [ Hide ]
 
 $test = new ReflectionClass( 'RegistrationModel ' ); // <------ here's a trailing space
 var_dump( $test->getConstants() );
 


I noticed a space in the classname in the above fragment. Was that intentional?
User avatar
Weirdan
Moderator
 
Posts: 5864
Joined: Mon Nov 03, 2003 7:13 pm
Location: Odessa, Ukraine


Return to Testing

Who is online

Users browsing this forum: gpqop1dsai and 0 guests