在Laravel中测试控制器的正确方法是什么?

时间:2014-05-30 16:29:58

标签: php unit-testing laravel mockery

我正在重写现有的Laravel 4应用程序,以确保有足够的测试。长话短说,我用TDD方法重写了我的AccountController课程,我遇到了一些麻烦。

考虑以下呈现包含用户列表的页面的方法:

public function getIndex()
{
    // build the view
    //
    return \View::make('account.list-users')
        ->with('users', \Sentry::getUserProvider()->findAll());
}

我正在使用Smarty渲染我的视图和Sentry进行身份验证。

现在,我想写一些像这样的测试:

public function test_getIndex()
{
    // arrange
    //

    // set up some mocks here...


    // act
    //
    $response = $this->client->request("GET", "/list-users");


    // assert
    //

    // test for <table class="table">
    $this->assertFalse($response->filter("table.table")==null, "table not found");

    // test for some <a> tags for the "update" buttons
    $element = $response->filter("td a")->first()->extract(array("href", "class", "_text"));
    $this->assertTrue(strstr($element[0][0],"/my-update-url")!="");
    $this->assertTrue(strstr($element[0][1],"btn btn-xs btn-success")!="");
    $this->assertTrue(strstr($element[0][2],"Active")!="");

    // test for some other markup...

}

我一直在关注Jeffrey Way的书 Laravel Testing Decoded 并编写如上所述的测试,并且它们工作正常。

头痛出现在“在这里设置一些嘲笑......”部分。具体来说,我需要设置的嘲讽数量是荒谬的。这是因为,作为更大的Web应用程序的一部分,我正在使用一个View组合器,它将数据添加到View模型:当前用户模型,菜单结构,警报消息,新闻消息,应用程序版本号等.I通过使用“裸骨”模板进行测试已经减少了很多,但它仍然是很多东西 - 我正在编写数百行代码来测试这个简单的单行方法。

有更好的方法吗?

我看到它的方式有两种方法:

一个。我一直这样做的方式

B中。模拟\View::make调用,以便绕过我的所有模板渲染 - 类似这样的

public function test_getIndex()
{
    // arrange
    //
    $userList = "this is a list of users";

    $userProvider = Mockery::mock("\Cartalyst\Sentry\Users\Eloquent\Provider");

    \Sentry::shouldReceive("getUserProvider")
        ->once()
        ->andReturn($userProvider);

    $userProvider->shouldReceive("findAll")
        ->once()
        ->andReturn($userList);

    $view = Mockery::mock("\Illuminate\View\View");

    \View::shouldReceive("make")
        ->with("account.list-users")
        ->once()
        ->andReturn($view);

    $view->shouldReceive("with")
        ->with("users", $userList)
        ->once()
        ->andReturn($view);

    $view->shouldReceive("render")
        ->once()
        ->andReturn("results");

    // act
    //
    $response = $this->call("GET", "/list-users");

    // assert
    //
    $this->assertResponseOk();
}

如果我采用这种方法,测试更简单,我只测试实际在控制器方法中的代码,但是我并没有真正测试调用该路由所涉及的所有内容(这可能是一件好事或者可能不会 - 我不确定)我担心我不会得到足够的报道。

那么,这样做的最佳方式是什么:(A),(B)还是其他什么?

修改

我对控制器方法的测试存在相当大的困惑,@ TheShiftExchange的回答更清楚了。评论如下。我将尝试在这里解决这个问题,作为一个编辑,因为它给了我更多的空间来讨论这个问题。

考虑以下答案中给出的第二个例子:

public function testMethod()
{
    $this->call('GET', '/list-users');

    $this->assertViewHas('users', \Sentry::getUserProvider()->findAll());
}

如果我运行此测试,它将起作用,但它将访问数据库,我试图通过模拟一些东西来避免。

所以,我可以稍微扩展一下这个测试:

public function testMethod()
{
    \Sentry::shouldReceive("getUserProvider")
        ->once()
        ->andReturn($userProvider);
    // plus a mock of the UserProvider class,...


    $this->call('GET', '/list-users');

    $this->assertViewHas('users', \Sentry::getUserProvider()->findAll());
}

此测试将工作,因为除了控制器方法所需的模拟之外,我还需要对我的View编辑器中的代码进行模拟。此代码包括$currentUser = \Sentry::getUser()(用户的名字显示在我的应用程序页面的右上角)。

所以代码实际上变成了:

public function testMethod()
{
    \Sentry::shouldReceive("getUserProvider")
        ->once()
        ->andReturn($userProvider);
    // plus a mock of the UserProvider class,...

    // plus a mock of ThisClass

    // and a mock of ThatClass

    // and a mock of SomeOtherClass

    // etc.

    // etc.

    $this->call('GET', '/list-users');

    $this->assertViewHas('users', \Sentry::getUserProvider()->findAll());
}

它很快就会失控。

这告诉我,我做错了什么,但我不确定是什么。我怀疑问题源于我对这里测试的确切性的不确定性。

所以,经过这一切,问题就变成了这个:

当我测试控制器的方法时,我真正想测试的是什么?

  1. 控制器方法中的代码?或者,

  2. 从请求到回复的整个过程?

  3. 我想测试的是第一项 - 只是控制器方法中的代码。我的问题中的示例非常简单,但我确实有一些控制器方法可以执行表单验证或基于用户输入的重定向 - 我想测试该代码。

    也许,我不是通过$this->call()测试代码,而是直接调用控制器方法?

1 个答案:

答案 0 :(得分:1)

作为Laravel框架的一部分,it includes some testing helpers。在这些助手中包括视图测试助手:

断言视图有一些数据

public function testMethod()
{
    $this->call('GET', '/');

    $this->assertViewHas('name');
    $this->assertViewHas('age', $value);
}

所以你可以这样做:

public function testMethod()
{
    \Sentry::shouldReceive("getUserProvider")
    ->once()
    ->andReturn('foo');

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

    $this->assertViewHas('users', 'foo');
}