为CakePHP 2中使用AuthComponent的控制器编写单元测试


Write unit test for a controller that uses AuthComponent in CakePHP 2

我正在尝试测试允许用户配置文件版本的控制器动作。除其他事项外,我想测试每个登录用户只能编辑自己的配置文件,而不能编辑其他人的配置文件。如果违反此限制,操作必须重定向到预定义的主页。

在这个场景中,我有一个创建ID = 1的用户的fixture。所以我在考虑这样测试限制:

$data = $this->Users->User->read(null, 1); 
$this->Users->Auth->login($data); 
$this->testAction('/users/edit/2', array('method' => 'get')); 
$url = parse_url($this->headers['Location']); 
$this->assertEquals($url['path'], '/homepage'); 

测试通过此断言。因此,下一步是检查执行具有登录用户ID的'/users/edit/1'是否显示如下形式:

$this->testAction('/users/edit/1', array('method' => 'get', 'return' => 'vars'));
$matcher = array( 
  'tag' => 'form', 
  'ancestor' => array('tag' => 'div'), 
  'descendant' => array('tag' => 'fieldset'), 
); 
$this->assertTag($matcher, $this->vars['content_for_layout'], 'The edition form was not found');

但是这个断言失败。在挖掘了debug()之后,我发现$this->Auth->user()返回了全部信息,但$this->Auth->user('id')返回了null。由于我在操作中的比较中使用了后者,因此它的计算结果为false并导致测试失败

奇怪的是,它发生在测试时,而不是在浏览器中执行操作。那么,测试这个动作的正确方法是什么呢?

谢谢!

实际的正确答案应该是使用模拟对象,而不是实际手动登录用户:

$this->controller = $this->generate('Users', array(
    'components' => array('Auth' => array('user')) //We mock the Auth Component here
));
$this->controller->Auth->staticExpects($this->once())->method('user') //The method user()
    ->with('id') //Will be called with first param 'id'
    ->will($this->returnValue(2)) //And will return something for me
$this->testAction('/users/edit/2', array('method' => 'get')); 

使用mock是测试控制器最简单的方法,也是最灵活的方法

2015年3月11日更新

你也可以模拟AuthComponent的所有方法

$this->controller = $this->generate('Users', array(
    'components' => array('Auth') // Mock all Auth methods
));

我喜欢Jose的回答,但是当面对类似的情况时,我想使用实际的AuthComponent和Session来创建一个可以给我信心的测试。

我使用基于控制器的身份验证,这意味着我的应用程序中的每个控制器必须提供自己的isAuthorized()回调。我想测试MyController::isAuthorized()。使用mock让测试通过似乎太容易了。

所以,我没有使用TestCase::generate()来创建一个带有模拟组件的模拟控制器,而是按照Mark Story的优秀文章测试CakePHP控制器,并提供了我自己的模拟控制器,用真正的CakePHP AuthComponent登录用户。

这是我的作品。请参阅顶部附近的方法testsauthorized()和MockAnnouncementsController的类def。

在我看来,CakePHP测试框架假设您只想通过requestAction()测试控制器。它的设计目的不是为了方便在不模拟AuthComponent和其他组件的情况下直接对控制器内的回调实现(如Controller::isAuthorized())进行单元测试,这将使我对测试该特定方法缺乏信心。尽管如此,我认为这是一个有效的用例,用于单元测试控制器的非操作部分(例如:"index"、"view"),但不能委托给组件,因为它们必须由核心框架调用。我还在考虑如何抽象它,使其可用于任何控制器

而不是:

$this->Auth->user('id')

试试这些:

$this->Auth->data['User']['id']
$this->Session->read('Auth.User.id')

设置如下:

$this->Users->Session->write('Auth.User', 
    array('id' => 1,'and_other_fields_you_need' => 'whatever')
);  

Mark Story在CakePHP票证中给了我答案。我需要像这样记录用户日志:

$data = $this->Users->User->read(null, 1); 
$this->Users->Auth->login($data['User']);
不是

$data = $this->Users->User->read(null, 1); 
$this->Users->Auth->login($data);