在 PHPUnit 中模拟依赖注入 URL 参数

Mocking Dependency Injected URL parameter in PHPUnit

我正在尝试为我编写的功能创建测试。

逻辑很简单:

来自 api.php 我正在调用存储方法:

Route::group(['prefix' => '/study/{study}/bookmark_list'], function () {
           ...
            Route::post('/{bookmarkList}/bookmark', 'BookmarkController@store');
           ...
        });

因此我正在注入学习和书签列表。

我的控制器传递参数

 public function store(Study $study, BookmarkList $bookmarkList)
    {
        return $this->serve(CreateBookmarkFeature::class);
    }

我相应地在功能中使用它们

'bookmark_list_id' => $request->bookmarkList->id,

class CreateBookmarkFeature extends Feature
{
    public function handle(CreateBookmarkRequest $request)
    {

        //Call the appropriate job
        $bookmark = $this->run(CreateBookmarkJob::class, [
            'bookmark_list_id' => $request->bookmarkList->id,
            'item_id'          => $request->input('item_id'),
            'type'             => $request->input('type'),
            'latest_update'    => $request->input('latest_update'),
            'notes'            => $request->input('notes')
        ]);

        //Return
        return $this->run(RespondWithJsonJob::class, [
            'data' => [
                'bookmark' => $bookmark
            ]
        ]);
    }
}

我也在使用自定义请求 (CreateBookmarkRequest),它实际上验证用户是否获得授权并对输入施加一些规则。

class CreateBookmarkRequest extends JsonRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return $this->getAuthorizedUser()->canAccessStudy($this->study->id);
    }


    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            "item_id"       => ["integer", "required"],
            "type"          => [Rule::in(BookmarkType::getValues()), "required"],
            "latest_update" => ['date_format:Y-m-d H:i:s', 'nullable'],
            "text"          => ["string", "nullable"]
        ];
    }
}

现在,问题来了。我想为测试返回正确响应的功能编写一个测试(验证调用 CreateBookmarkJob 会很好,但不是那么重要)。问题是虽然我可以模拟请求以及 input() 方法,但我无法模拟注入的 bookmarkList.

其余函数已正确模拟并按预期工作。

我的测试:

class CreateBookmarkFeatureTest extends TestCase
{
    use WithoutMiddleware;
    use DatabaseMigrations;

    public function setUp(): void
    {
        parent::setUp();

        // seed the database
        $this->seed();
    }

    public function test_createbookmarkfeature()
    {
        //GIVEN
        $mockRequest = $this->mock(CreateBookmarkRequest::class);
        $mockRequest->shouldReceive('authorize')->once()->andReturnTrue();
        $mockRequest->shouldReceive('rules')->once()->andReturnTrue();
        $mockRequest->shouldReceive('input')->once()->with('item_id')->andReturn(1);
        $mockRequest->shouldReceive('input')->once()->with('type')->andReturn("ADVOCATE");
        $mockRequest->shouldReceive('input')->once()->with('latest_update')->andReturn(Carbon::now());
        $mockRequest->shouldReceive('input')->once()->with('notes')->andReturn("acs");
        $mockRequest->shouldReceive('bookmark_list->id')->once()->andReturn(1);
        

        //WHEN
        $response = $this->postJson('/api/recruitment_toolkit/study/1/bookmark_list/1/bookmark', [
            "type"=> "ADVOCATE",
            "item_id"=> "12",
            "text"=> "My first bookmark"
        ]);
        
        //THEN
        $this->assertEquals("foo", $response['data'], "das");
    }

我认为一个可能的解决方案是不模拟请求,但这样我就找不到在请求中模拟“returnAuthorisedUser”的方法。

任何关于如何模拟注入模型的想法,或者任何关于如何正确测试该功能以防我处理错误的想法。

值得一提的是,我对每个作业(CreateBookmarkJob 和 RespondWithJSONJob)都有单独的单元测试。

提前致谢

根据定义,功能测试将模仿最终用户的操作。无需模拟请求 class,您只需像用户一样发出请求。

假设您的播种器已创建 ID 为 1 的 Study 和 ID 为 1 的 BookmarkList,Laravel 将通过路由模型绑定注入适当的依赖项。如果没有,您应该使用工厂方法创建模型,然后在 URL.

中替换适当的 ID
<?php

namespace Tests\Feature;

use Tests\TestCase;

class CreateBookmarkFeatureTest extends TestCase
{
    use WithoutMiddleware;
    use DatabaseMigrations;

    public function setUp(): void
    {
        parent::setUp();
        $this->seed();
    }

    public function TestCreateBookmarkFeature()
    {
        $url = '/api/recruitment_toolkit/study/1/bookmark_list/1/bookmark';
        $data = [
            "type"=> "ADVOCATE",
            "item_id"=> "12",
            "text"=> "My first bookmark"
        ];
        $this->postJson($url, $data)
            ->assertStatus(200)
            ->assertJsonPath("some.path", "some expected value");
    }
}

我同意@miken32 的回应——一个功能确实应该模仿用户交互——但是通过路由模型绑定的依赖注入仍然不起作用。

花了几个小时后,我意识到它的原因是

use WithoutMiddleware;

禁用所有中间件,甚至是负责路由模型绑定的中间件,因此不会在请求中注入对象模型。

实际的解决方案是(对于 laravel >=7)我们可以定义我们想要禁用的中间件,在这种情况下:

$this->withoutMiddleware(\App\Http\Middleware\Authenticate::class);

那我们就用

$user = User::where('id',1)->first(); $this->actingAs($user);

其他一切都按预期工作。

免责声明:我并不是暗示 miken32 的回复不正确;这绝对是正确的方向 - 只是将其添加为一个小细节。