控制器的 Laravel 单元测试

Posted

技术标签:

【中文标题】控制器的 Laravel 单元测试【英文标题】:Laravel unit testing of controllers 【发布时间】:2014-09-13 07:07:51 【问题描述】:

我正在尝试按照 TDD 启动一个新的 Laravel 应用

我的第一步是检查 /login 控制器是否在 home url 上调用。

尽管遵循了几个教程,但我无法进行测试,而且我根本看不出我做错了什么。

我的设置是: 作曲家安装 laravel 作曲家安装phpunit

这是我的路线:

<?php
Route::get('/login', 'AuthenticationController@login');

我的控制器:

<?php

class AuthenticationController extends BaseController 

    public function login () 
        return View::make('authentication.login');
    


我的测试:

<?php

class AuthenticationTest extends TestCase 

    public function testSomeTest () 

        $response = $this->action('GET', 'AuthenticationController@login');

        $view = $response->original;

        $this->assertEquals('authentication.login', $view['name']);
    

我得到的错误是

  ErrorException: Undefined index: name

从 Laravel 站点复制的代码(几乎完全一样),但它没有运行。

谁能看出我做错了什么?

它声称 $view 没有索引名称,但这不能像 laravel 网站上的示例那样正确,而且视图正在使用它的名称呈现(它也在前端正确显示)

编辑::

因此,从评论看来,laravel 单元测试部分并不清楚,并且 $view['name'] 正在检查一个名为 $name 的变量。如果是这种情况,你如何测试使用的控制器/路由,IE。路由('X')使用了什么控制器名称/动作名称

【问题讨论】:

$view['name'] 不是视图的名称,而是视图上可用的变量 $name。但是你没有传递任何东西给它。在您的控制器中,它应该是例如return View::make('authentication.login')-&gt;with('name', 'John');,然后您想测试 $name 是否可从视图中获得并包含“John”:$this-&gt;assertEquals('John', $view['name']); 真的吗?然后文档具有误导性。那么我如何测试我的控制器是否在某个路线上触发?或者我如何获得用于特定路线的控制器/动作的名称? 我不太确定你想要达到什么目的。如果您想对应用程序进行单元测试,这不涉及测试路由是否调用了正确的控制器方法或控制器是否加载了正确的视图。这是 Laravel 功能的一部分,并且已经过测试。您想测试是否调用了控制器中的正确方法以及响应是什么。你不需要测试哪个路由调用哪个控制器方法,你只需调用触发你真正想要测试的控制器方法的路由。 我想这是有道理的。我的想法是 id 编写一个测试,登录操作将被称为默认/主路由。那么如果用户未登录,我会检查另一条路由是否重定向到登录路由。但是如果我无法获取正在使用的路由或控制器,我将如何测试未经授权的用户是否被重定向到登录路由/控制器?我想 $response = $this->call('GET', 'admin/users'); $this->assertRedirectedTo('admin/login');是我唯一能做的事情(尽管它确实做了我想做的事。 是的,因为这就是您要测试的内容。在您的单元测试中,您只想知道是否根据用户是否登录触发了正确的操作。在您的情况下,您检查响应是否是重定向到某个路由。就是这样。您不在乎之后会发生什么或调用哪个控制器 - 这超出了单元测试的范围。您只想检查您的应用程序是否对给定的输入给出了预期的响应。设置经过身份验证的用户,调用路由并检查您是否被重定向到“管理员/用户”,否则例如到“用户/登录”。 【参考方案1】:

2020 年 7 月 1 日更新:

由于这个答案似乎仍然不时得到一些支持,我只想指出 我不再认为这是一个好的测试方法。自 v4 以来,Laravel 的测试体验有了很大的改进,整体范式发生了相当大的转变,从单元和类转向功能和端点。这不仅是一种惯用的变化,而且从技术角度来看似乎更有意义。

此外,从那时起,我们还引入了许多新的有用的测试助手,这些助手允许进行不那么脆弱的测试。请参阅文档以获取概览和基本测试示例。


好的,正如在 cmets 中已经解释的那样,让我们​​先退一步考虑一下场景。

“我的第一步是检查 /login 控制器是否在 home url 上被调用。”

这意味着:当用户点击主路由时,您想检查用户是否登录。如果他没有登录,您希望将他们重定向到登录,可能带有一些闪烁消息。他们登录后,您希望将他们重定向回主页。如果登录失败,您希望将它们重定向回登录表单,也可以使用一条消息。

所以现在有几件事要测试:家庭控制器和登录控制器。因此,遵循 TDD 精神,让我们先创建测试。

注意:我将遵循phpspec 使用的一些命名约定,但不要让这困扰您。

class HomeControllerTest extends TestCase

    /**
     * @test
     */
    public function it_redirects_to_login_if_user_is_not_authenticated()
    
        Auth::shouldReceive('check')->once()->andReturn(false);

        $response = $this->call('GET', 'home');
        
        // Now we have several ways to go about this, choose the
        // one you're most comfortable with.

        // Check that you're redirecting to a specific controller action 
        // with a flash message
        $this->assertRedirectedToAction(
             'AuthenticationController@login', 
             null, 
             ['flash_message']
        );
        
        // Only check that you're redirecting to a specific URI
        $this->assertRedirectedTo('login');

        // Just check that you don't get a 200 OK response.
        $this->assertFalse($response->isOk());

        // Make sure you've been redirected.
        $this->assertTrue($response->isRedirection());
    

    /**
     * @test
     */
    public function it_returns_home_page_if_user_is_authenticated()
    
        Auth::shouldReceive('check')->once()->andReturn(true);

        $this->call('GET', 'home');

        $this->assertResponseOk();
    

Home 控制器就是这样。在大多数情况下,您实际上并不关心您被重定向到哪里,因为这可能会随着时间的推移而改变,您将不得不更改测试。因此,您至少应该检查您是否被重定向,并且仅在您确实认为这对您的测试很重要时才检查更多详细信息。

让我们看看 Authentication 控制器:

class AuthenticationControllerTest extends TestCase

    /**
     * @test
     */
    public function it_shows_the_login_form()
    
        $response = $this->call('GET', 'login');

        $this->assertTrue($response->isOk());

        // Even though the two lines above may be enough,
        // you could also check for something like this:

        View::shouldReceive('make')->with('login');
    

    /**
     * @test
     */
    public function it_redirects_back_to_form_if_login_fails()
    
        $credentials = [
            'email' => 'test@test.com',
            'password' => 'secret',
        ];

        Auth::shouldReceive('attempt')
             ->once()
             ->with($credentials)
             ->andReturn(false);

        $this->call('POST', 'login', $credentials);

        $this->assertRedirectedToAction(
            'AuthenticationController@login', 
            null, 
            ['flash_message']
        );
    

    /**
     * @test
     */
    public function it_redirects_to_home_page_after_user_logs_in()
    
        $credentials = [
            'email' => 'test@test.com',
            'password' => 'secret',
        ];

        Auth::shouldReceive('attempt')
             ->once()
             ->with($credentials)
             ->andReturn(true);

        $this->call('POST', 'login', $credentials);

        $this->assertRedirectedTo('home');
    

同样,始终考虑您真正想要测试的内容。你真的需要知道哪个控制器动作是在哪个路由上触发的吗?或者返回的视图名称是什么?实际上,您只需要确保控制器确实尝试 这样做。你向它传递一些数据,然后测试它的行为是否符合预期。

并始终确保您没有尝试测试任何框架功能,例如特定路由是否触发特定操作或视图是否正确加载。这已经过测试,因此您无需担心。关注应用程序的功能,而不是底层框架。

【讨论】:

你对模拟Auth::shouldReceive('check')方法有什么问题吗?当我尝试使用该方法执行您的测试方法时,我得到driver() does not exist on this mock object @extra90 是的,它工作得很好,而且它是记录在案的模拟外观的方式:laravel.com/docs/5.1/testing#mocking-facades。听起来您没有设置会话或身份验证驱动程序。 感谢您的快速回复。是的,我有自定义身份验证驱动程序。那么你知道如何模拟检查方法和自定义驱动程序吗? @extra90 抱歉,我对此了解不多。也许看看框架本身是如何工作的可能会有所帮助:github.com/laravel/framework/blob/5.1/src/Illuminate/Support/… - 简而言之,它只是用模拟交换了 'auth' 容器绑定。实际上,这应该适用于任何司机......但我从未与其他司机合作过,所以我恐怕无法真正帮助你。 是的,也许你提到的会话驱动程序也有问题。当我尝试使用Auth attempt 方法时效果很好。模拟会话驱动程序的最佳方法是什么?

以上是关于控制器的 Laravel 单元测试的主要内容,如果未能解决你的问题,请参考以下文章

在 Laravel 中对控制器进行单元测试而不测试路由的最佳方法是啥

使用 PHPUnit 在 Laravel 控制器中进行单元测试 Guzzle

Laravel 单元测试断言函数总是失败

在 laravel 5.2 单元测试中模拟作业

Laravel 单元测试显示完全错误

Laravel - 输入未通过单元测试