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

为在CakePHP 2中使用AuthComponent的控制器编写单元测试,php,unit-testing,cakephp,authentication,Php,Unit Testing,Cakephp,Authentication,我正在尝试测试一个允许编辑用户配置文件的控制器操作。除此之外,我想测试每个登录用户只能编辑自己的配置文件,而不能编辑其他用户的配置文件。如果违反此限制,操作必须重定向到预定义的主页 在这个场景中,我有一个fixture来创建ID=1的用户。所以我想用这种方式测试限制: $data = $this->Users->User->read(null, 1); $this->Users->Auth->login($data); $this->testActi

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

在这个场景中,我有一个fixture来创建ID=1的用户。所以我想用这种方式测试限制:

$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->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')
);  
马克的故事给了我答案。基本上,我必须这样记录用户:

$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);

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

$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
));
我喜欢,但当面临类似情况时,我希望使用实际的AuthComponent和Session创建一个能够给我信心的测试

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

所以,我没有使用TestCase::generate()创建带有模拟组件的模拟控制器,而是遵循Mark Story的优秀文章,提供了我自己的模拟控制器,它使用真正的CakePHP组件登录用户

给你。请参阅顶部附近MockAnnouncementsController的testIsAuthorized()方法和类def


在我看来,CakePHP测试框架假设您只想通过requestAction()测试控制器。它的设计目的不是为了在不模仿AuthComponent和其他组件的情况下,在控制器内方便对回调实现(如Controller::isAuthorized())进行直接单元测试,这会降低我对该特定方法测试的信心。然而,我认为这是一个有效的用例,用于单元测试控制器中不是动作的部分(例如“索引”、“视图”),但不能委托给组件,因为它们必须由核心框架调用。我仍在考虑如何将其抽象出来,使其可用于任何控制器。

它也不起作用。这些方法不是意味着等价吗?如果必须使用
generate()
的话,那么使用
ControllerTestCase
有什么意义呢?generate()仅在ControllerTestCase中可用,它的提供是为了使您的testAction()更容易,例如能够模拟控制器方法、组件,模型等。如果您自己不调用generate(),那么testAction()将使用CakePHP默认值(模拟_stop()和redirect()函数)在内部为您执行此操作。我认为
generate()
实际上做了其他事情。这就是为什么我一直在想,如果
ControllerTestCase
为我提供了默认配置,为什么要使用它。我会试试你的答案,让你知道:)不管怎样,你有什么理由认为手动登录用户是不正确的吗?我的意思是,我用虚拟数据进行测试,不应该有任何风险…是的,节省时间!无论是编程时间还是执行时间,mock当您开始运行大量的测试用例时,对数据库的调用数量将很快降低您的执行速度。关键是在不需要时保留它们和最小值。此外,这不仅仅适用于AuthComponent,想想任何其他组件,如Cookie或安全性!你会如何为这两个人伪造数据和环境?快速的答案总是模拟对象。