用于(实际)单元测试的Laravel 5.1中的模拟请求

Chr*_*sco 11 tdd unit-testing laravel laravel-5 laravel-5.1

首先,我知道文档说明:

注意:您不应该模拟请求外观.相反,在运行测试时,将您想要的输入传递给HTTP帮助程序方法,例如调用和发布.

但是那些类型的测试更像是集成或功能,因为即使你正在测试一个控制器(SUT你),你也不会将它与它的依赖关系脱钩(Request和其他人一样,稍后会更多).

所以我在做什么,为了做正确的TDD循环,被嘲讽Repository,ResponseRequest(我有问题).

我的测试看起来像这样:

public function test__it_shows_a_list_of_categories() {
    $categories = [];
    $this->repositoryMock->shouldReceive('getAll')
        ->withNoArgs()
        ->once()
        ->andReturn($categories);
    Response::shouldReceive('view')
        ->once()
        ->with('categories.admin.index')
        ->andReturnSelf();
    Response::shouldReceive('with')
        ->once()
        ->with('categories', $categories)
        ->andReturnSelf();

    $this->sut->index();

    // Assertions as mock expectations
}
Run Code Online (Sandbox Code Playgroud)

这非常好用,它们遵循Arrange,Act,Assert风格.

问题在于Request,如下所示:

public function test__it_stores_a_category() {
    Redirect::shouldReceive('route')
        ->once()
        ->with('categories.admin.index')
        ->andReturnSelf();

    Request::shouldReceive('only')
        ->once()
        ->with('name')
        ->andReturn(['name' => 'foo']);

    $this->repositoryMock->shouldReceive('create')
        ->once()
        ->with(['name' => 'foo']);

    // Laravel facades wont expose Mockery#getMock() so this is a hackz
    // in order to pass mocked dependency to the controller's method
    $this->sut->store(Request::getFacadeRoot());

    // Assertions as mock expectations
}
Run Code Online (Sandbox Code Playgroud)

如你所见,我嘲笑了Request::only('name')电话.但是当我运行时,$ phpunit我收到以下错误:

BadMethodCallException: Method Mockery_3_Illuminate_Http_Request::setUserResolver() does not exist on this mock object
Run Code Online (Sandbox Code Playgroud)

因为我不直接setUserResolver()从我的控制器调用,这意味着它是由执行直接调用的Request.但为什么?我模拟了方法调用,它不应该调用任何依赖.

我在这里做错了什么,为什么我收到此错误消息?

PS:作为奖励,我通过在Laravel框架上使用单元测试强制TDD来查看它是错误的方式,因为看起来文档通过耦合依赖关系和SUT之间的交互来进行集成测试$this->call()

Amo*_*Amo 7

使用Laravel时对控制器进行单元测试似乎不是一个好主意.考虑到Controller的上下文,我不会关注在Request,Response甚至是存储库类上调用的各个方法.

此外,单元测试属于框架,因为要解耦它的依赖测试SUT是没有意义的,因为你只能使用控制器与那些依赖给出该框架的控制器.

由于请求,响应和其他类都经过全面测试(通过底层Symfony类或Laravel本身),作为开发人员,我只关心测试我拥有的代码.

我会写一个验收测试.

<?php

use App\User;
use App\Page;
use App\Template;
use App\PageType;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class CategoryControllerTest extends TestCase
{
    use DatabaseTransactions;

    /** @test */
    public function test__it_shows_a_paginated_list_of_categories()
    {
        // Arrange
        $categories = factory(Category::class, 30)->create();

        // Act
        $this->visit('/categories')

        // Assert
            ->see('Total categories: 30')
            // Additional assertions to verify the right categories can be seen may be a useful additional test
            ->seePageIs('/categories')
            ->click('Next')
            ->seePageIs('/categories?page=2')
            ->click('Previous')
            ->seePageIs('/categories?page=1');
    }

}
Run Code Online (Sandbox Code Playgroud)

因为这个测试使用了DatabaseTransactions特征,所以很容易执行过程的排列部分,这几乎可以让你把它读作伪单元测试(但这只是想象力的一小部分).

最重要的是,此测试验证了我的期望得到满足.我的测试被调用test_it_shows_a_paginated_list_of_categories,我的测试版本就是这样.我觉得单元测试路由只断言调用了一堆方法,但是并没有验证我是否在页面上显示给定类别的列表.