在 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 的回复不正确;这绝对是正确的方向 - 只是将其添加为一个小细节。
我正在尝试为我编写的功能创建测试。
逻辑很简单:
来自 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.
<?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 的回复不正确;这绝对是正确的方向 - 只是将其添加为一个小细节。