Laravel 测试身份验证。使用 Illuminate\Database\Eloquent\ModelNotFoundException 的后续登录尝试失败
Posted
技术标签:
【中文标题】Laravel 测试身份验证。使用 Illuminate\\Database\\Eloquent\\ModelNotFoundException 的后续登录尝试失败【英文标题】:Laravel testing Authentication. Failed on subsequent login attemtps with Illuminate\Database\Eloquent\ModelNotFoundExceptionLaravel 测试身份验证。使用 Illuminate\Database\Eloquent\ModelNotFoundException 的后续登录尝试失败 【发布时间】:2021-10-24 03:45:45 【问题描述】:我正在尝试使用不同的用户角色测试 Laravel。
我进行了 4 次测试(实际上,用 2 种不同的方法进行了 2 次测试):
use RefreshDatabase;
/** @test */
public function if_administrators_can_view_user_list_post()
$this->seed();
$user = User::find(1);
$credentials = [
'email' => $user->email,
'password' => 'password'
];
$response = $this->post(route('login'), $credentials);
$this->get(route('admin.users.index'))
->assertStatus(200);
$this->post(route('logout'));
/** @test */
public function if_users_cannot_view_user_list_post()
$this->seed();
$user = User::find(2);
$credentials = [
'email' => $user->email,
'password' => 'password'
];
$response = $this->post(route('login'), $credentials);
$this->get(route('admin.users.index'))
->assertStatus(403);
$this->post(route('logout'));
/** @test */
public function if_administrators_can_view_user_list_auth()
$this->seed();
$user = User::find(1);
if (Auth::attempt(['email' => $user->email, 'password' => 'password']))
$this->get(route('admin.users.index'))
->assertStatus(200);
Auth::logout();
else
$this->assertTrue(false);
/** @test */
public function if_users_cannot_view_user_list_auth()
$this->seed();
$user = User::find(2);
if (Auth::attempt(['email' => $user->email, 'password' => 'password']))
$this->get(route('admin.users.index'))
->assertStatus(403);
Auth::logout();
else
$this->assertTrue(false);
数据库以 2 users
为种子。用户id = 1
是管理员,用户id = 2
是普通用户。
如果我分别运行每个测试,所有测试都可以:
PS C:\laravel> php artisan test --filter if_administrators_can_view_user_list_post
Warning: TTY mode is not supported on Windows platform.
PASS Tests\Feature\Routes\UserRoutesTest
✓ if administrators can view user list post
Tests: 1 passed
Time: 1.51s
PS C:\laravel> php artisan test --filter if_users_cannot_view_user_list_post
Warning: TTY mode is not supported on Windows platform.
PASS Tests\Feature\Routes\UserRoutesTest
✓ if users cannot view user list post
Tests: 1 passed
Time: 1.54s
PS C:\laravel> php artisan test --filter if_administrators_can_view_user_list_auth
Warning: TTY mode is not supported on Windows platform.
PASS Tests\Feature\Routes\UserRoutesTest
✓ if administrators can view user list auth
Tests: 1 passed
Time: 1.45s
PS C:\laravel> php artisan test --filter if_users_cannot_view_user_list_auth
Warning: TTY mode is not supported on Windows platform.
PASS Tests\Feature\Routes\UserRoutesTest
✓ if users cannot view user list auth
Tests: 1 passed
Time: 1.50s
但是,如果我运行所有测试,第二个和其他测试都因 Illuminate\Database\Eloquent\ModelNotFoundException 异常而失败:
PS C:\laravel> php artisan test
FAIL Tests\Feature\Routes\UserRoutesTest
✓ if administrators can view user list post
⨯ if users cannot view user list post
⨯ if administrators can view user list auth
⨯ if users cannot view user list auth
• Tests\Feature\Routes\UserRoutesTest > if users cannot view user list post
Illuminate\Database\Eloquent\ModelNotFoundException
No query results for model [App\Models\User] 1
at C:\laravel\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Builder.php:434
430▕ elseif (! is_null($result))
431▕ return $result;
432▕
433▕
➜ 434▕ throw (new ModelNotFoundException)->setModel(
435▕ get_class($this->model), $id
436▕ );
437▕
438▕
1 C:\laravel\vendor\laravel\framework\src\Illuminate\Support\Traits\ForwardsCalls.php:23
Illuminate\Database\Eloquent\Builder::findOrFail()
2 C:\laravel\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Model.php:1993
Illuminate\Database\Eloquent\Model::forwardCallTo(Object(Illuminate\Database\Eloquent\Builder), "findOrFail")
• Tests\Feature\Routes\UserRoutesTest > if administrators can view user list auth
Illuminate\Database\Eloquent\ModelNotFoundException
No query results for model [App\Models\User] 1
at C:\laravel\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Builder.php:434
430▕ elseif (! is_null($result))
431▕ return $result;
432▕
433▕
➜ 434▕ throw (new ModelNotFoundException)->setModel(
435▕ get_class($this->model), $id
436▕ );
437▕
438▕
1 C:\laravel\vendor\laravel\framework\src\Illuminate\Support\Traits\ForwardsCalls.php:23
Illuminate\Database\Eloquent\Builder::findOrFail()
2 C:\laravel\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Model.php:1993
Illuminate\Database\Eloquent\Model::forwardCallTo(Object(Illuminate\Database\Eloquent\Builder), "findOrFail")
• Tests\Feature\Routes\UserRoutesTest > if users cannot view user list auth
Illuminate\Database\Eloquent\ModelNotFoundException
No query results for model [App\Models\User] 1
at C:\laravel\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Builder.php:434
430▕ elseif (! is_null($result))
431▕ return $result;
432▕
433▕
➜ 434▕ throw (new ModelNotFoundException)->setModel(
435▕ get_class($this->model), $id
436▕ );
437▕
438▕
1 C:\laravel\vendor\laravel\framework\src\Illuminate\Support\Traits\ForwardsCalls.php:23
Illuminate\Database\Eloquent\Builder::findOrFail()
2 C:\laravel\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Model.php:1993
Illuminate\Database\Eloquent\Model::forwardCallTo(Object(Illuminate\Database\Eloquent\Builder), "findOrFail")
Tests: 3 failed, 1 passed
Time: 1.84s
有人知道这件事吗?
【问题讨论】:
【参考方案1】:好的,让我们先解决一些小细节:
-
切勿编写在同一测试中断言 2 个不同(或更多)行为的测试。测试应该只测试一件事。
if_administrators_can_view_user_list_auth
,应该是 user_list_vieable_by_administrator
或 user_list_vieable_when_user_is_logged_in_as_administrator
或类似的东西。你也必须创建逆向,user_list_not_vieable_when_user_is_not_admin
,在这个中你可以测试所有可能的非管理员类型的用户类型。
永远不要将测试名称写成条件(至少使用if
)。例如,如果您想测试用户是否可以在 X 应用程序中发布消息,请将测试编写为 test_user_can_create_post_when_logged_in
而不是 if_user_is_logged_in_create_post
或类似名称。这是一种约定。阅读用户故事和验收标准,有一种写法,你应该遵循验收标准的写作约定。
我个人的建议不是盲目播种,而是只在您的数据库中拥有您要测试的内容,无论您是否为永远不会使用的模型创建条目,也许这可能会改变您系统中的某些内容,而您没有考虑到这一点。
假设用户在发布内容时不需要上传Avatar
,但您的代码错误并且您需要头像。因为您正在严格地播种任何东西(甚至是您的测试不需要的东西),所以您的测试可以通过,但在幕后,您需要它才能工作,您关注我吗?
请记住,您可以在 PHPUnit 上使用 dataProvider
s 为您的测试提供不同的数据,例如我在此处提到的第 2 点。无需创建许多不同类型的用户,您可以提供他们并对每种类型运行一个测试,因此当任何失败时,您可以看到哪个失败了,并且您的测试不会因设置数据而变得臃肿。
测试时切勿使用route
助手。例如,如果你想要的路由是POST /user/post
,名称是user.post.store
,你会做$this->post(route('user.post.store'), [...]);
,但是如果user.post.store
URL 是/asd/123/bad/url-5555
呢?您不是在测试您访问所需的 URL 并执行您想要的操作。所以你必须测试所有东西,即使你必须像机器人一样打字,你必须测试所有东西,而不是依赖任何东西,因为它是一个功能测试,所以你正在测试你能做的一切.如果您可以更改框架并且代码仍然相同,它应该仍然可以工作(假设您不使用 route
助手),它应该仍然可以通过您想要的 URL 等访问。不要使用助手,除非断言的东西。
因此,为了帮助您多一点,您不应该一味地播种所有内容。请记住,您可以使用$this->seed(SeederName::class);
,如果您使用->seed();
,它将使用默认播种器。您甚至可以传递一个包含多个播种器的数组。
我现在要重写您的测试,以便您了解可以做得更好的地方:
/** @test */
public function user_list_vieable_when_user_is_logged_in_as_administrator()
/** @var User $user Remember this user is an admin, set up that */
$user = User::factory()->create();
$response = $this->actingAs($user)
->get('/admin/users');
$response->assertOk();
该测试与if_administrators_can_view_user_list_post
完全相同,但请注意:
$this->actingAs($model, 'guard');
,甚至可以指定使用哪个守卫作为第二个参数。
您不需要退出,因为下一个测试将被“重置”,因此您将没有会话。新鲜如新的测试。
您也可以使用一些“同义词”,例如assertOk()
与->assertStatus(200)
完全相同,您可以使用->assertSuccessful()
即status >= 200 && status < 300
。但这确实是个人的。查看所有available asserts,了解更多信息。
现在让我们继续其他的:
/** @test */
public function user_list_forbidden_when_user_is_not_admin()
/** @var User $user Remember this user is not an admin, set up that */
$user = User::factory()->create();
$response = $this->actingAs($user)
->get('/admin/users');
$response->assertForbidden();
再次,我刚刚创建了具有所需权限的用户,并针对所需的路由进行了测试,然后断言它是被禁止的。我个人会返回 401
(unauthorized),因为 403
更特别,但非常相似。
在这种情况下,您可以像我之前提到的那样使用dataProvider
并测试每种类型的用户,因此您可以 100% 确定任何不是管理员或您想要的任何类型的用户都将被禁止。
让我们快速“模拟”它,因为我不知道您的架构。假设我们在您的 User
表中有一个 role
列,因此您可以这样做:
/**
* @test
* @dataProvider
*/
public function user_list_forbidden_when_user_is_not_admin(string $type)
/** @var User $user */
$user = User::factory()->$type()->create();
$response = $this->actingAs($user)
->get('/admin/users');
$response->assertForbidden();
public function common_user_types_data_provider()
return [
'Normal' => ['normal'],
'Moderator' => ['moderator'],
'Editor' => ['editor'],
'Vip' => ['vip']
];
我利用了states
,但是您可以发送任何您需要的东西,除了必须在框架准备好之后运行的数据或类。 dataProvider
s 在框架准备好之前运行,所以你不能使用任何关于它的东西。
按照您的测试顺序,我不能 100% 确定您想在 if_administrators_can_view_user_list_auth
和 if_users_cannot_view_user_list_auth
中测试什么,但我假设您想测试与之前的测试类似的东西,但如果用户没有登录。所以在之前的测试中,我们没有测试用户甚至没有登录时会发生什么,直到那个测试我们总是假设用户使用角色登录,现在让我们测试用户甚至没有登录时会发生什么在:
/** @test */
public function guest_user_should_get_redirected_to_login_when_viewing_admin_users_dashboard()
// As we are guests, we directly access the URL, we do not need to do anything else
$response = $this->get('/admin/users');
$response->assertRedirect('/login');
看我用过的测试命名,你应该总是用should
而不是must
,when
而不是if
。
【讨论】:
感谢您的建议,@matiaslauriti。我刚开始在 Laravel 上进行测试,因此,您的见解非常有帮助。我进行了所有更改以保持接近您的方法,并且似乎使用 actAs($user) 不起作用。两个测试都以相同的 Illuminate\Database\Eloquent\ModelNotFoundException 失败。即使我单独运行测试。actingAs
不会抛出该错误,该错误来自其他地方,如果您仍在使用seeders
,则存在问题。您可以使用错误图像更新您的问题吗?检查堆栈跟踪是否可见,以便我可以看到它来自哪里。
有这方面的消息吗?以上是关于Laravel 测试身份验证。使用 Illuminate\Database\Eloquent\ModelNotFoundException 的后续登录尝试失败的主要内容,如果未能解决你的问题,请参考以下文章
Laravel 测试身份验证。使用 Illuminate\Database\Eloquent\ModelNotFoundException 的后续登录尝试失败
如何使用 API 中间件在 Laravel 中测试 PHPUnit 一个经过身份验证的用户?