CakePHP控制器测试:模拟Auth组件

Nic*_*ick 5 testing cakephp

情况

控制器代码

<?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.';
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

浏览器输出

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
Run Code Online (Sandbox Code Playgroud)

测试代码

<?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'));
    }
}
Run Code Online (Sandbox Code Playgroud)

测试输出

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: 
Run Code Online (Sandbox Code Playgroud)

问题

这里实际上有三个问题:

  1. 测试代码非常重复.
  2. 测试输出中的第二条"已修改"行为空白.它应该是"2013-05-08 00:00:00",就像在浏览器的输出中一样.
  3. 如果我要修改控制器代码,在"Username"和"Created" echo 'Email: ' . $this->Auth->user('email') . '<br>';之间添加一行(例如)echo,测试将失败并显示以下错误: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')在不破坏测试的情况下在任何地方添加代码?

red*_*rdo 9

在回答这个问题之前,我不得不承认我没有使用CakePHP框架的经验.但是,我有很多使用PHPUnit和Symfony框架的经验,并遇到过类似的问题.为了解决你的观点:

  1. 请参阅我对第3点的回答

  2. 这样做的原因是您需要一个额外的...->staticExpects($this->at(5))...声明来涵盖对Auth-> user()的第6次调用.这些语句没有定义使用指定值对Auth-> user()的任何调用返回的值.它们定义了例如对Auth对象的第二次调用必须是带有参数'username'的方法user(),在这种情况下将返回'admin'.但是,如果您在下一点遵循该方法,这应该不再是一个问题.

  3. 我假设你在这里尝试实现的是独立于Auth组件测试你的控制器(因为坦白说测试控制器对用户对象进行一系列的getter调用是没有意义的).在这种情况下,模拟对象被设置为存根以始终返回特定的结果集,而不是期望具有特定参数的特定系列调用(请参阅存根上的PHP手册条目).这可以通过代码中的'$ this-> any()'替换'(x)'处的'$ this->来实现.然而,虽然这会否定我需要添加第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'));
    }
    
    Run Code Online (Sandbox Code Playgroud)

这将允许您的控制器更新,以便根据您的喜好以任何顺序获取用户属性,前提是模拟对象已经返回这些属性.无论控制器是否以及多久检索一次,都可以编写模拟对象以返回所有用户属性(或者可能都与此控制器相关).(请注意,在您的具体示例中,如果您的模拟包含'email',控制器中的pr()语句将从测试中输出与浏览器不同的结果,但我认为您不希望能够将新属性添加到记录中无需更新测试).

以这种方式编写测试意味着您的控制器编辑功能需要是这样的 - 一个更可测试的版本:

$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.';
}
Run Code Online (Sandbox Code Playgroud)

据我所知,Hash :: get($ record,$ key)是从记录中检索属性的正确CakePHP方法,尽管你在这里有简单的属性,我认为用户[$ key]也能正常工作.