【问题标题】:Properly Mocking a CakePHP Controller's referer()正确模拟 CakePHP 控制器的 referer()
【发布时间】:2014-07-03 09:46:42
【问题描述】:

我正在使用 CakePHP,构建一个只有帖子和 cmets 的简单博客站点。我烘焙了模型、控制器和视图。当您查看帖子时,它在底部有一个“相关评论”部分,显示该帖子的所有 cmets。每个评论都有操作按钮(“查看”、“编辑”和“删除”)。我注意到,如果您编辑或删除评论,您不会返回您正在查看的帖子。相反,您将被带到 cmets 索引。当您查看帖子并单击底部的“新评论”按钮并为该帖子添加新评论时,也会发生同样的事情。我想修改此行为,以便在您查看帖子并编辑或删除现有评论或添加新评论时,您将被重定向回帖子。我在Comments 控制器中为这三个操作(“添加”、“编辑”和“删除”)中的每一个添加了代码。这个问题的重点是“删除”动作。这就是我所做的:

app/Controller/CommentsController.php中的原始代码

public function delete($id = null) {
    $this->Comment->id = $id;
    if (!$this->Comment->exists()) {
        throw new NotFoundException(__('Invalid comment'));
    }
    $this->request->allowMethod('post', 'delete');
    if ($this->Comment->delete()) {
        $this->Session->setFlash(__('The comment has been deleted.'));
    } else {
        $this->Session->setFlash(__('The comment could not be deleted. Please, try again.'));
    }
    return $this->redirect(array('action' => 'index'));
}

修改代码app/Controller/CommentsController.php

public function delete($id = null) {
    /*
     * Same as above minus the redirect line (the following code replaces
     * the redirect line).
     */
    // Redirect to `$this->referer()` unless `$this->referer()`
    // is this comment's "edit" or "view" action (we do not want to
    // redirect the user to a 404 page), in which case redirect them
    // to the comments index.

    $referer = $this->referer();

    if ($referer) {
        $comingFromEditPage =
            mb_strpos(
                $referer,
                Router::url(array('action' => 'edit', $id), true)
            ) === 0;

        if (!$comingFromEditPage) {
            $comingFromViewPage =
                mb_strpos(
                    $referer,
                    Router::url(array('action' => 'view', $id), true)
                ) === 0;
        }

        if (!$comingFromEditPage && !$comingFromViewPage) {
            $redirectUrl = $referer;
        }
    }

    if (!isset($redirectUrl)) {
        $redirectUrl = array('action' => 'index');
    }

    return $this->redirect($redirectUrl);
}

我想在我的 app/Test/Case/Controller/CommentsControllerTest.php 文件中测试这种行为,所以我让它看起来像这样:

CommentsControllerTest.php

<?php
App::uses('CommentsController', 'Controller');

class CommentsControllerTest extends ControllerTestCase {

    public $fixtures = array(
        'app.comment',
        'app.post'
    );

    public function tearDown() {
        parent::tearDown();
        unset($this->Controller);
    }

    public function testDeleteReferer() {
        // When the referer is the comment's "edit" action, deleting the
        // comment should redirect to the comments index.

        $this->Controller = $this->generate('Comments', array(
            'methods' => array(
                'referer'
            )
        ));

        $this->Controller->expects($this->any())->method('referer')->will(
            $this->returnValue(
                Configure::read('App.fullBaseUrl') .
                $this->Controller->request->base .
                Router::url(
                    array(
                        'controller' => 'comments',
                        'action' => 'edit',
                        '1',
                    )
                )
            )
        );

        $commentsIndexUrl =
            Configure::read('App.fullBaseUrl') .
            $this->Controller->request->base .
            Router::url(
                array(
                    'controller' => 'comments',
                    'action' => 'index',
                )
            );

        $this->testAction(
            '/comments/delete/1',
            array('method' => 'post')
        );

        $this->assertEqual(
            $this->headers['Location'],
            $commentsIndexUrl
        );

        // When the referer is the comment's "view" action, deleting the
        // comment should redirect to the comments index.

        $this->Controller = $this->generate('Comments', array(
            'methods' => array(
                'referer'
            )
        ));

        $this->Controller->expects($this->any())->method('referer')->will(
            $this->returnValue(
                Configure::read('App.fullBaseUrl') .
                $this->Controller->request->base .
                Router::url(
                    array(
                        'controller' => 'comments',
                        'action' => 'view',
                        '2',
                    )
                )
            )
        );

        $this->testAction(
            '/comments/delete/2',
            array('method' => 'post')
        );

        // The following assertion fails on the command line!

        $this->assertEqual(
            $this->headers['Location'],
            $commentsIndexUrl
        );
    }

}

如上面代码中所述,第二个assertEqual 在命令行上失败。当我运行cake test --stderr app Controller/CommentsController 时,输出如下:

Welcome to CakePHP v2.5.2 Console
---------------------------------------------------------------
App : app
Path: /Applications/MAMP/htdocs/blogtest/app/
---------------------------------------------------------------
CakePHP Test Shell
---------------------------------------------------------------
F

Time: 0 seconds, Memory: 18.50Mb

There was 1 failure:

1) CommentsControllerTest::testDeleteReferer
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'http://localhost/Applications/MAMP/htdocs/blogtest/lib/Cake/Console/comments'
+'http://localhost/Applications/MAMP/htdocs/blogtest/lib/Cake/Console/Applications/MAMP/htdocs/blogtest/lib/Cake/Console/comments/view/2'

/Applications/MAMP/htdocs/blogtest/lib/Cake/TestSuite/CakeTestCase.php:552
/Applications/MAMP/htdocs/blogtest/app/Test/Case/Controller/CommentsControllerTest.php:93
/Applications/MAMP/htdocs/blogtest/lib/Cake/TestSuite/CakeTestCase.php:82
/Applications/MAMP/htdocs/blogtest/lib/Cake/TestSuite/CakeTestRunner.php:60
/Applications/MAMP/htdocs/blogtest/lib/Cake/TestSuite/CakeTestSuiteCommand.php:96
/Applications/MAMP/htdocs/blogtest/lib/Cake/Console/Command/TestShell.php:274
/Applications/MAMP/htdocs/blogtest/lib/Cake/Console/Command/TestShell.php:259
/Applications/MAMP/htdocs/blogtest/lib/Cake/Console/Shell.php:440
/Applications/MAMP/htdocs/blogtest/lib/Cake/Console/ShellDispatcher.php:207
/Applications/MAMP/htdocs/blogtest/lib/Cake/Console/ShellDispatcher.php:66

FAILURES!
Tests: 1, Assertions: 2, Failures: 1.

知道为什么即使测试在浏览器中运行良好,为什么会在命令行上发生这种情况吗?我该如何解决?

【问题讨论】:

  • 我不明白你为什么要经历这些麻烦。如果删除失败,只需重定向回引荐来源网址,如果删除成功,则重定向回 index 非常简单。一个简单的 if/else 就足够了。将 if 条件拆分为多行也会使代码难以阅读。如果您确实需要帮助,请遵守约定。
  • @user221931 为什么我的目标是在删除失败时重定向回引荐来源网址,如果删除成功则重定向到 index?就个人而言,我认为我用于if 语句的格式比这更容易阅读:if ($this-&gt;referer() &amp;&amp; !(mb_strpos($this-&gt;referer(), Router::url(array('action' =&gt; 'edit', $id), true)) === 0) &amp;&amp; !(mb_strpos($this-&gt;referer(), Router::url(array('action' =&gt; 'view', $id), true)) === 0)) {。你不同意吗?另外,我没有遵循哪些约定,这如何使获得帮助变得更加困难?
  • 那么你的目标是什么?不要以这种态度面对我,您可以阅读 cakephp 的预期编码标准是什么,也许反思为什么您没有得到任何答案。
  • @user221931 我的目标显示在“app/Controller/CommentsController.php 中的修改代码”部分中“删除”操作顶部的注释中。以这样的态度面对你?我说什么粗鲁了?此外,我通过CakePHP Code Sniffer 运行了我的代码,除了我的测试还没有函数 doc cmets 之外,这很好。所以我仍然不确定你指的是什么。最后,您似乎在暗示我的代码格式化方式与没有答案有关。我是否正确解释了您的评论?
  • 我认为您的回复过于激进,我深表歉意。这是陌生人之间书面交流的产物。你的代码中有...,所以我不知道你在做什么,但我希望你删除评论。除非评论被删除,否则不想重定向到 404 是没有意义的。所以你的方法看起来很奇怪,如果你接受我的建议,可以更容易地解决/测试。分解if 语句也是不好的做法。如果它变得太复杂并且不能这样做,否则使用临时变量。太难读的代码意味着更少的人会费心。

标签: cakephp


【解决方案1】:

这并不能回答您的确切问题,但我相信可以更快、更清晰地解决您的问题,并且更容易理解和测试。

如果删除成功,您希望重定向到索引(以免导致 404),如果删除失败,您希望重定向到引荐来源网址。

public function delete($id = null) {
    if ($this->Comment->delete($id)) { //If comment deletion succeeds
        return $this->redirect(array('action' => 'index'); //Redirect to index
    } 
    return $this->redirect($this->referer()); //Else redirect to referrer
}

注意return 将如何停止进一步执行,因此您实际上不需要else。这段代码现在只有 3 行简单的代码,并且只有一个缩进级别。

此外,我认为这段代码几乎不需要任何测试,因为它基于经过大量测试的核心功能。您可以相信 Model::delete()Controller::redirect() 会完成他们的工作,如果您相信这些,您可以相信控制器方法也会按预期执行。我认为测试模型比控制器更重要(并不是说控制器应该被排除在外,但我只费心测试复杂或容易发生变化/错误的东西)。

因此,如果您仍需要进行极端测试,您可以将$_SERVER['HTTP_REFERER'] 设置为您喜欢的任何值(CakeRequest 将选择$this-&gt;referer())并查看它是否是您被重定向到的 url删除失败后。

【讨论】:

  • 感谢您的回答,但实际上我要做的只是对cake bake 生成的标准delete 操作进行轻微修改。无论删除是否成功,该操作始终重定向到 array('action' =&gt; 'index')。我要做的就是将重定向位置从array('action' =&gt; 'index') 更改为$this-&gt;referer(),但前提是$this-&gt;referer() 不是评论的编辑页面或查看页面。想要测试该功能可能看起来很奇怪,但我仍然想要,即使只是为了满足我对测试代码有什么问题的好奇心。
  • 你能提供完整的蛋糕烘焙代码吗?刚刚烘焙了一个测试控制器,得到的代码几乎和我之前写的一样。
  • 是的,确认最近的版本烘焙代码是这样的。但是不要把它想象成你不应该接触的代码,它是大多数简单案例的模板,你应该扩展到你的需要。毕竟我不确定我是否会将您的代码归类为轻微修改 - 尽管我会我的:) 所以我认为已经足够祝你好运了。
  • 我不认为cake bake 代码是你不应该接触的代码。就代码量而言,我的改变并不小。就描述它而言,它很小。 “而不是总是重定向到 cmets 索引,它仅在引用者是评论的编辑或查看页面时重定向到那里,否则它重定向到引用者”。我奖励你是因为你给了我一个修复(设置$_SERVER['HTTP_REFERER'])。我还决定将此标记为正确,因为它告诉我如何解决问题,尽管它没有解释问题发生的原因。
  • 如果将来您添加另一个不想重定向回来的方法怎么办?你会记得重新访问你的代码并手动添加另一个字符串比较和一个测试吗?如果您更改路线并且edit 在网址中被称为其他名称怎么办?这就是为什么我说我认为您的代码不是“小”修改,而且每次运行时都会进行硬编码字符串比较。感谢您的赏金,虽然这不是我的目标,但我相信如果您放下最初的想法并重新审视问题,这种方法无论如何都会“更好”。
猜你喜欢
  • 2012-05-13
  • 2012-02-27
  • 2013-05-03
  • 2012-05-21
  • 2011-11-06
  • 2014-04-22
  • 2018-12-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多