CakePHP控制器测试:模拟验证组件 [英] CakePHP Controller Testing: Mocking the Auth Component

查看:109
本文介绍了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.';
        }
    }
}

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'));
    }
}

测试输出 / p>

Output from Test

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: 



问题



这里有三个问题:

The Problem

There are actually three problems here:


  1. 测试代码非常重复。

  2. 测试输出中的修改行为空白。它
    应该是从浏览器的输出2013-05-08 00:00:00。

  3. 如果我要修改控制器代码,添加一行表示 echo'Email:'。 $ this-> Auth-> user('email')。在用户名和创建的 echo 之间的< br>'; 此错误:方法名的预期失败等于< string:user>当在序列索引2 时调用。这是有道理的,因为 $ this->在(1)不再是真的。

  1. The test code is very repetitive.
  2. The second "Modified" line in the output from the test is blank. It should be "2013-05-08 00:00:00" like in the output from the browser.
  3. If I were to modify the controller code, adding a line that said echo 'Email: ' . $this->Auth->user('email') . '<br>'; (just for example) between the echoing of "Username" and "Created", the test would fail with this error: Expectation failed for method name is equal to <string:user> when invoked at sequence index 2. This makes sense since the $this->at(1) is no longer true.



我的问题



如何模拟Auth组件的方式是(1)不重复,(2)导致测试输出作为浏览器,(3)允许我在任何地方添加 $ this-> Auth-> user('foo') p>

My Question

How can I mock the Auth component in a way that (1) is not repetitive, (2) causes the test to output the same thing as the browser, and (3) allows me to add $this->Auth->user('foo') code anywhere without breaking the tests?

推荐答案

在我回答之前,我必须承认我没有使用CakePHP框架的经验。但是,我有一个相当多的经验使用PHPUnit与Symfony框架,并遇到类似的问题。要解决您的问题:

Before I answer this I have to admit that I've no experience of using the CakePHP framework. However, I have a fair amount of experience working with PHPUnit in conjunction with the Symfony framework and have encountered similar issues. To address your points:


  1. 查看我对第3点的回答

  1. See my answer to point 3

这样做的原因是你需要一个额外的 ...-> staticExpects($ this-> at(5))... 语句覆盖第6次调用Auth-> user()。这些语句不定义对具有指定值的任何调用Auth-> user()的返回值。他们定义对Auth对象的第二次调用必须是具有参数username的方法user(),在这种情况下将返回admin。

The reason for this is that you need an additional ...->staticExpects($this->at(5))... statement to cover the 6th call to Auth->user(). These statements do not define the values to return for any call to Auth->user() with the specified value. They define that e.g. the 2nd call to the Auth object must be to method user() with parameter 'username' in which case 'admin' will be returned. However, this should no longer be an issue if you follow the approach in the next point.

我假设你在这里尝试达到这个目的,但是这不应该是一个问题。是独立于Auth组件测试您的控制器(因为坦率地说,测试控制器对用户对象进行一系列getter调用没有意义)。在这种情况下,模拟对象被设置为存根,以总是返回特定的结果集合,而不是期望具有特定参数的特定系列调用(请参阅存根的PHP手动输入)。只需在代码中用'$ this-> any()'替换'$ this-> at(x)',就可以然而,虽然这将无需添加额外的行我提到的第2点,你仍然会有重复。遵循TDD方法在代码之前编写测试,我建议如下:

I am making the assumption that what you are trying to achieve here is to test your controller independently of the Auth component (because frankly it doesn't make sense to test that a controller makes a series of getter calls on a user object) . In this case a mock object is set up as a stub to always return a particular set of results, rather than to expect a specific series of calls with particular parameters (See PHP Manual entry on stubs). This could be done just be replacing '$this->at(x)' with '$this->any()' in your code. However, whilst this would negate the need to add the extra line I mentioned in point 2, you'd still have the repetition. Following the TDD approach of writing tests before code, I'd suggest the following:

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'));
}


控制器更新为可以根据任何顺序获取用户属性,只要它们已经由模拟对象返回即可。 您的模拟对象可以写成返回所有用户属性(或者可能所有可能与此控制器相关),而不管控制器检索它们的频率和频率。 (注意,在你的具体示例中,如果你的模拟包含'电子邮件'控制器中的pr()语句将输出不同于浏览器的测试结果,但我假设你不希望能够添加新的属性到记录

This would allow your controller to be updated to make as many calls as you like to get user attributes in any order provided they are already returned by the mock object. Your mock object could be written to return all user attributes (or perhaps all likely to be relevant to this controller) regardless of whether and how often the controller retrieves them. (Note in your specific example if your mock contains 'email' the pr() statement in the controller will output different results from the test than the browser but I am presuming you don't expect to be able to add new attributes to a record without having to update your tests).

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

Writing the test this way means your controller edit function would need to be something like this - a more testable version:

$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 key)是正确的CakePHP方法来从记录中检索属性,虽然使用简单的属性你在这里我认为用户[$ key]也可以工作。

As far as I can gather, Hash::get($record, $key) is the correct CakePHP way to retrieve attributes from a record although with the simple attributes you have here I presume user[$key] would work just as well.

这篇关于CakePHP控制器测试:模拟验证组件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆