情况
Controller 代码
<?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.';
}
}
}
浏览器输出
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
测试代码
<?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'));
}
}
测试输出
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”。
- 如果我要修改 Controller 代码,添加一行表示
echo 'Email: ' . $this->Auth->user('email') . '<br>';
(仅举个例子)在echo
之间ing 的“用户名”和“已创建”,测试将失败并出现此错误:Expectation failed for method name is equal to <string:user> when invoked at sequence index 2
.这是有道理的,因为$this->at(1)
不再是真的。
我的问题
我如何以 (1) 不重复的方式模拟 Auth 组件,(2) 使测试输出与浏览器相同的内容,以及 (3) 允许我添加 $this->Auth->user('foo')
在不破坏测试的情况下在任何地方编写代码?
最佳答案
在回答这个问题之前,我必须承认我没有使用 CakePHP 框架的经验。但是,我有相当多的使用 PHPUnit 和 Symfony 框架的经验,并且遇到过类似的问题。解决您的问题:
请参阅我对第 3 点的回答
这样做的原因是您需要一个额外的
...->staticExpects($this->at(5))...
语句来覆盖对 Auth 的第 6 次调用->用户()。这些语句没有定义使用指定值调用 Auth->user() 时要返回的值。他们定义了例如对 Auth 对象的第二次调用必须是使用参数“username”的方法 user(),在这种情况下,将返回“admin”。但是,如果您遵循下一点中的方法,这应该不再是问题。我假设您在这里尝试实现的是独立于 Auth 组件测试您的 Controller (因为坦率地说,测试 Controller 对一个用户对象)。在这种情况下,模拟对象被设置为 stub 以始终返回一组特定的结果,而不是期望具有特定参数的特定系列调用(See PHP Manual entry on stubs)。这可以只需在您的代码中将“$this->at(x)”替换为“$this->any()”即可完成。然而,虽然这会消除添加我在第 2 点中提到的额外行的需要,但您仍然会重复。按照在代码之前编写测试的 TDD 方法,我建议如下:
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' => 'me@me.com', ))); $this->testAction('/posts/edit/1', array('method' => 'get')); }
这将允许您的 Controller 进行更新以进行任意数量的调用以按任意顺序获取用户属性前提是它们已经由模拟对象返回。您的模拟对象可以编写为返回所有用户属性(或者可能所有可能与此 Controller 相关的属性),而不管 Controller 是否以及多久检索一次它们。 (请注意,在您的特定示例中,如果您的模拟包含“电子邮件”,则 Controller 中的 pr() 语句将输出与浏览器不同的测试结果,但我假设您不希望能够向记录添加新属性无需更新您的测试)。
以这种方式编写测试意味着您的 Controller 编辑功能需要像这样 - 一个更可测试的版本:
$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.';
}
据我所知,Hash::get($record, $key) 是从记录中检索属性的正确 CakePHP 方法,尽管使用这里的简单属性我认为 user[$key] 可以正常工作
关于CakePHP Controller 测试 : Mocking the Auth Component,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16448178/