PHP Developers Network

A community of PHP developers offering assistance, advice, discussion, and friendship.
 
Loading
It is currently Sat Nov 22, 2014 5:08 am

All times are UTC - 5 hours




Post new topic Reply to topic  [ 9 posts ] 
Author Message
 Post subject: PHPUnit + Mock
PostPosted: Fri May 09, 2008 4:41 am 
Offline
Forum Newbie

Joined: Tue Feb 21, 2006 11:37 am
Posts: 14
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.

Top
 Profile  
 
 Post subject: Re: PHPUnit + Mock
PostPosted: Fri May 09, 2008 7:05 am 
Offline
Moderator
User avatar

Joined: Mon Nov 03, 2003 7:13 pm
Posts: 5919
Location: Odessa, Ukraine
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


Top
 Profile  
 
 Post subject: Re: PHPUnit + Mock
PostPosted: Fri May 09, 2008 11:58 am 
Offline
Forum Newbie

Joined: Tue Feb 21, 2006 11:37 am
Posts: 14
Quote:
'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.


Top
 Profile  
 
 Post subject: Re: PHPUnit + Mock
PostPosted: Fri May 09, 2008 3:47 pm 
Offline
Moderator
User avatar

Joined: Mon Nov 03, 2003 7:13 pm
Posts: 5919
Location: Odessa, Ukraine
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.
formatting


Top
 Profile  
 
 Post subject: Re: PHPUnit + Mock
PostPosted: Tue May 13, 2008 4:59 am 
Offline
Forum Newbie

Joined: Tue Feb 21, 2006 11:37 am
Posts: 14
:)

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:
Quote:
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.


Top
 Profile  
 
 Post subject: Re: PHPUnit + Mock
PostPosted: Tue May 13, 2008 8:24 am 
Offline
Forum Newbie

Joined: Tue Feb 21, 2006 11:37 am
Posts: 14
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:
Quote:
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 :_____(


Top
 Profile  
 
 Post subject: Re: PHPUnit + Mock
PostPosted: Tue May 13, 2008 9:12 am 
Offline
Breakbeat Nuttzer
User avatar

Joined: Wed Mar 24, 2004 8:57 am
Posts: 13098
Location: Melbourne, Australia
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.


Top
 Profile  
 
 Post subject: Re: PHPUnit + Mock
PostPosted: Tue May 13, 2008 10:04 am 
Offline
Forum Newbie

Joined: Tue Feb 21, 2006 11:37 am
Posts: 14
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?


Top
 Profile  
 
 Post subject: Re: PHPUnit + Mock
PostPosted: Sat May 31, 2008 6:36 pm 
Offline
Moderator
User avatar

Joined: Mon Nov 03, 2003 7:13 pm
Posts: 5919
Location: Odessa, Ukraine
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.

Quote:
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?


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 9 posts ] 

All times are UTC - 5 hours


Who is online

Users browsing this forum: No registered users and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Jump to:  
Powered by phpBB® Forum Software © phpBB Group