Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock a CakePHP behavior for unit testing

I've just started with unit testing in CakePHP (yay!) and ran into the following challenge. Hope somebody can help me :-)

Situation

My model uses a Behavior to send changes to an API after saving it locally. I would like to fake all calls made to the API during the test (those will be tested seperately) to save load on the API server, and more important, not actually save the changes :-)

I'm using CakePHP 2.4.1.

What I've tried

  • Read the docs. The manual shows how to do this for Components and Helpers but not for Behaviors.
  • Google. What I've found:
    • A Google Group post which says it simply "isn't possible". I don't take no for an answer.
    • An article explaining how to mock an object. Comes pretty close.

The code from the article reads:

$provider = $this->getMock('OurProvider', array('getInfo'));
$provider->expects($this->any())
    ->method('getInfo')
    ->will($this->returnValue('200'));

It might be the wrong direction, but I think that might be a good start.

What I want

Effectively: A snippet of code to demo how to mock a behavior in a CakePHP Model for unit testing purposes.

Maybe this question will result in an addition of the CakePHP manual too as an added bonus, since I feel it's missing in there.

Thanks in advance for the effort!

Update (2013-11-07)

I've found this related question, which should answer this question (partly). No need to mock up the API, instead I can create a Behavior test that the model will use.

I'm trying to figure out what that BehaviorTest should look like.

like image 834
Coen Coppens Avatar asked Nov 07 '13 10:11

Coen Coppens


1 Answers

Use the class registry

As with many classes, behaviors are added to the class registry using the class name as the key, and for subsequent requests for the same object loaded from the classregistry. Therefore, the way to mock a behavior is simply to put it in the class registry before using it.

Full Example:

<?php
App::uses('AppModel', 'Model');

class Example extends AppModel {

}
class TestBehavior extends ModelBehavior {

    public function foo() {
        throw new \Exception('Real method called');
    }
}

class BehaviorExampleTest extends CakeTestCase {

    /**
     * testNormalBehavior
     *
     * @expectedException Exception
     * @expectedExceptionMessage Real method called
     * @return void
     */
    public function testNormalBehavior() {
        $model = ClassRegistry::init('Example');
        $model->Behaviors->attach('Test');

        $this->assertInstanceOf('TestBehavior', $model->Behaviors->Test);
        $this->assertSame('TestBehavior', get_class($model->Behaviors->Test));
        $this->assertSame(['foo' => ['Test', 'foo']], $model->Behaviors->methods());

        $model->foo();
    }

    public function testMockedBehavior() {
        $mockedBehavior = $this->getMock('TestBehavior', ['foo', 'bar']);
        ClassRegistry::addObject('TestBehavior', $mockedBehavior);

        $model = ClassRegistry::init('Example');
        $model->Behaviors->attach('Test');

        $this->assertInstanceOf('TestBehavior', $model->Behaviors->Test);
        $this->assertNotSame('TestBehavior', get_class($model->Behaviors->Test));

        $expected = [
            'foo' => ['Test', 'foo'],
            'bar' => ['Test', 'bar'],
            'expects' => ['Test', 'expects'], // noise, due to being a mock
            'staticExpects' => ['Test', 'staticExpects'], // noise, due to being a mock
        ];
        $this->assertSame($expected, $model->Behaviors->methods());

        $model->foo(); // no exception thrown

        $mockedBehavior
            ->expects($this->once())
            ->method('bar')
            ->will($this->returnValue('something special'));

        $return = $model->bar();
        $this->assertSame('something special', $return);
    }
}
like image 176
AD7six Avatar answered Sep 28 '22 00:09

AD7six