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

时间:2013-05-08 18:57:24

标签: cakephp testing

情况

控制器代码

<?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: 

问题

这里实际上有三个问题:

  1. 测试代码非常重复。
  2. 测试输出中的第二个“已修改”行为空白。它 应该是“2013-05-08 00:00:00”,就像在浏览器的输出中一样。
  3. 如果我要修改控制器代码,在“用户名”和“已创建”的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)不再是真的。
  4. 我的问题

    如何以(1)不重复的方式模拟Auth组件,(2)使测试输出与浏览器相同的内容,(3)允许我添加$this->Auth->user('foo')代码在没有破坏测试的地方?

1 个答案:

答案 0 :(得分:9)

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

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

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

  3. 我假设你在这里尝试实现的是独立于Auth组件测试你的控制器(因为坦率地说,测试一个控制器进行一系列的getter调用是没有意义的用户对象)。在这种情况下,模拟对象被设置为存根以始终返回特定的结果集,而不是期望具有特定参数(See PHP Manual entry on stubs)的特定系列调用。这个可以完成,只需在代码中用'$ this-&gt; any()'替换'$ this-&gt; at(x)'。然而,虽然这会否定我需要添加第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'));
    }
    
  4. 这将允许您的控制器更新以根据您的喜好进行任意数量的调用以获得用户属性,前提是它们已由模拟对象返回。无论控制器是否以及多久检索一次,都可以编写模拟对象以返回所有用户属性(或者可能都与此控制器相关)。 (请注意,在您的特定示例中,如果您的模拟包含'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.';
    }
    

    据我所知,Hash :: get($ record,$ key)是从记录中检索属性的正确CakePHP方法,尽管你在这里有简单的属性我假设用户[$ key]可以正常工作同样。