Controller Code
<?php
App::uses('AppController', 'Controller');
class PostsController extends AppController {
public function isAuthorized() {
return true;
}
public function edit($id = null) {
$this->autoRender = false;
if (!$this->Post->exists($id)) {
throw new NotFoundException(__('Invalid post'));
}
if ($this->Post->find('first', array(
'conditions' => array(
'Post.id' => $id,
'Post.user_id' => $this->Auth->user('id')
)
))) {
echo 'Username: ' . $this->Auth->user('username') . '<br>';
echo 'Created: ' . $this->Auth->user('created') . '<br>';
echo 'Modified: ' . $this->Auth->user('modified') . '<br>';
echo 'All:';
pr($this->Auth->user());
echo 'Modified: ' . $this->Auth->user('modified') . '<br>';
} else {
echo 'Unauthorized.';
}
}
}
Output from Browser
Username: admin
Created: 2013-05-08 00:00:00
Modified: 2013-05-08 00:00:00
All:
Array
(
[id] => 1
[username] => admin
[created] => 2013-05-08 00:00:00
[modified] => 2013-05-08 00:00:00
)
Modified: 2013-05-08 00:00:00
Test Code
<?php
App::uses('PostsController', 'Controller');
class PostsControllerTest extends ControllerTestCase {
public $fixtures = array(
'app.post',
'app.user'
);
public function testEdit() {
$this->Controller = $this->generate('Posts', array(
'components' => array(
'Auth' => array('user')
)
));
$this->Controller->Auth->staticExpects($this->at(0))->method('user')->with('id')->will($this->returnValue(1));
$this->Controller->Auth->staticExpects($this->at(1))->method('user')->with('username')->will($this->returnValue('admin'));
$this->Controller->Auth->staticExpects($this->at(2))->method('user')->with('created')->will($this->returnValue('2013-05-08 00:00:00'));
$this->Controller->Auth->staticExpects($this->at(3))->method('user')->with('modified')->will($this->returnValue('2013-05-08 00:00:00'));
$this->Controller->Auth->staticExpects($this->at(4))->method('user')->will($this->returnValue(array(
'id' => 1,
'username' => 'admin',
'created' => '2013-05-08 00:00:00',
'modified' => '2013-05-08 00:00:00'
)));
$this->testAction('/posts/edit/1', array('method' => 'get'));
}
}
Output from Test
Username: admin
Created: 2013-05-08 00:00:00
Modified: 2013-05-08 00:00:00
All:
Array
(
[id] => 1
[username] => admin
[created] => 2013-05-08 00:00:00
[modified] => 2013-05-08 00:00:00
)
Modified:
There are actually three problems here:
echo 'Email: ' . $this->Auth->user('email') . '<br>';
(just for example) between the echo
ing of "Username" and "Created", the test would fail with this error: Expectation failed for method name is equal to <string:user> when invoked at sequence index 2
. This makes sense since the $this->at(1)
is no longer true.How can I mock the Auth component in a way that (1) is not repetitive, (2) causes the test to output the same thing as the browser, and (3) allows me to add $this->Auth->user('foo')
code anywhere without breaking the tests?
Before I answer this I have to admit that I've no experience of using the CakePHP framework. However, I have a fair amount of experience working with PHPUnit in conjunction with the Symfony framework and have encountered similar issues. To address your points:
See my answer to point 3
The reason for this is that you need an additional ...->staticExpects($this->at(5))...
statement to cover the 6th call to Auth->user(). These statements do not define the values to return for any call to Auth->user() with the specified value. They define that e.g. the 2nd call to the Auth object must be to method user() with parameter 'username' in which case 'admin' will be returned. However, this should no longer be an issue if you follow the approach in the next point.
I am making the assumption that what you are trying to achieve here is to test your controller independently of the Auth component (because frankly it doesn't make sense to test that a controller makes a series of getter calls on a user object) . In this case a mock object is set up as a stub to always return a particular set of results, rather than to expect a specific series of calls with particular parameters (See PHP Manual entry on stubs). This could be done just be replacing '$this->at(x)' with '$this->any()' in your code. However, whilst this would negate the need to add the extra line I mentioned in point 2, you'd still have the repetition. Following the TDD approach of writing tests before code, I'd suggest the following:
public function testEdit() {
$this->Controller = $this->generate('Posts', array(
'components' => array(
'Auth' => array('user')
)
));
$this->Controller->Auth
->staticExpects($this->any())
->method('user')
->will($this->returnValue(array(
'id' => 1,
'username' => 'admin',
'created' => '2013-05-08 00:00:00',
'modified' => '2013-05-08 00:00:00',
'email' => '[email protected]',
)));
$this->testAction('/posts/edit/1', array('method' => 'get'));
}
This would allow your controller to be updated to make as many calls as you like to get user attributes in any order provided they are already returned by the mock object. Your mock object could be written to return all user attributes (or perhaps all likely to be relevant to this controller) regardless of whether and how often the controller retrieves them. (Note in your specific example if your mock contains 'email' the pr() statement in the controller will output different results from the test than the browser but I am presuming you don't expect to be able to add new attributes to a record without having to update your tests).
Writing the test this way means your controller edit function would need to be something like this - a more testable version:
$this->autoRender = false;
if (!$this->Post->exists($id)) {
throw new NotFoundException(__('Invalid post'));
}
$user = $this->Auth->user();
if ($this->Post->find('first', array(
'conditions' => array(
'Post.id' => $id,
'Post.user_id' => Hash::get($user, 'id')
)
))) {
echo 'Username: ' . Hash::get($user, 'username') . '<br>';
echo 'Created: ' . Hash::get($user, 'created') . '<br>';
echo 'Modified: ' . Hash::get($user, 'modified') . '<br>';
echo 'All:';
pr($user);
echo 'Modified: ' . Hash::get($user, 'modified') . '<br>';
} else {
echo 'Unauthorized.';
}
As far as I can gather, Hash::get($record, $key) is the correct CakePHP way to retrieve attributes from a record although with the simple attributes you have here I presume user[$key] would work just as well.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With