Question

I am testing a controller method which uses an AppModel method to get the account of the logged-in user. Problem is, I cannot effectively mock the AppModel method providing the account_id, instead getting null when debugging this value. Please let me know what I'm missing, or if I'm going about this the wrong way.

AppModel.php includes:

/**
 * _getUser
 *
 * @param string $key from AuthComponent::user
 * @return string value from AuthComponent::user
 */
    public function _getUser($key = null) {
        return AuthComponent::user($key);
    }

AccountsController.php includes:

App::uses('AppController', 'Controller');
class AccountsController extends AppController {

    public $name = 'Accounts';

/**
 * memberLetters -
 *  Generate a letter to each member
 *
 **/
    function memberLetters() {
        $account_id = $this->Account->_getUser('account_id');
        debug($account_id); // !!! returns null; expecting '1' !!!
    }
}

User Model (User.php) includes:

App::uses('AuthComponent', 'Controller/Component');
class User extends AppModel {
    public $name = 'User';
    public $actsAs = array('Acl' => array('type' => 'requester'));

    public $validate = array(
        'email' => array(
            'email' => array(
                'rule' => array('email'),
                'message' => 'Email address required'),
            'isUnique' => array(
                'rule' => array('isUnique'),
                'message' => 'This email address is already associated with an account.')
        ),
        'password' => array(
            'notempty' => array(
                'rule' => array('notempty'),
                'message' => 'Password required',
            ),
        ),
        'account_id' => array(
            'numeric' => array(
                'rule' => array('numeric'),
            ),
        ),
    );

    public $belongsTo = array('Account');
}

Test code: AccountsControllerTest.php includes:

App::uses('AccountsController', 'Controller');
class AccountsControllerTest extends ControllerTestCase {

/**
 * Fixtures
 *
 * @var array
 */
    public $fixtures = array(
        'app.account',
        'app.user',
    );

/**
 * testMemberLetters method
 *
 * @return void
 */
    public function testMemberLetters() {
        $model = $this->getMockForModel('Account', array('_getUser'), array());

        $model
            ->expects($this->exactly(1))
            ->method('_getUser', array('account_id'))
            ->will($this->returnValue(1));

        $result = $this->testAction(
            'accounts/memberLetters',
            array(
                'method' => 'get',
                'return' => 'vars',
            )
        );


 }
Was it helpful?

Solution

I resolved this by refactoring the Model::get_User() call out of the AccountsController code and into the Account model code. This made it testable. Thanks to Tim Koschützki http://debuggable.com/posts/implementing-different-types-for-cakes-modelfind-method:485030de-4778-456e-8400-44d84834cda3 I found the following way to override model->find:

Accounts.php includes

function find($type = string, $queryData = array()) {
    switch ($type) {
        case 'mine':
            if (!in_array('conditions', $queryData)) {
                $queryData['conditions'] =
                    array('id' => $this->_getUser('account_id'));
            } else {
                $queryData['conditions'] .=
                    array('id' => $this->_getUser('account_id'));
            }
            return parent::find('first', $queryData);
        default:
            return parent::find($type, $queryData);
    }
}

So now my AccountsController methods can simply call

$data = $this->Account->find('mine');

and in my AccountsControllerTest I write

public function testMemberLetters() {
    $this->controller = $this->generate('Accounts', array(
        'models' => array(
            'Account' => array('_getUser')
        )
    ));

    $this->controller->Account->expects($this->exactly(1))
        ->method('_getUser')
        ->with('account_id')
        ->will($this->returnValue(1));

    $result = $this->testAction(
        'accounts/memberLetters',
        array(
            'method' => 'get',
            'return' => 'vars',
        )
    );
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top