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_administratoruser_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 上使用 dataProviders 为您的测试提供不同的数据,例如我在此处提到的第 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,但是您可以发送任何您需要的东西,除了必须在框架准备好之后运行的数据或类。 dataProviders 框架准备好之前运行,所以你不能使用任何关于它的东西。

按照您的测试顺序,我不能 100% 确定您想在 if_administrators_can_view_user_list_authif_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而不是mustwhen而不是if

【讨论】:

感谢您的建议,@matiaslauriti。我刚开始在 Laravel 上进行测试,因此,您的见解非常有帮助。我进行了所有更改以保持接近您的方法,并且似乎使用 actAs($user) 不起作用。两个测试都以相同的 Illuminate\Database\Eloquent\ModelNotFoundException 失败。即使我单独运行测试。 actingAs 不会抛出该错误,该错误来自其他地方,如果您仍在使用seeders,则存在问题。您可以使用错误图像更新您的问题吗?检查堆栈跟踪是否可见,以便我可以看到它来自哪里。 有这方面的消息吗?

以上是关于Laravel 测试身份验证。使用 Illuminate\Database\Eloquent\ModelNotFoundException 的后续登录尝试失败的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 测试身份验证。使用 Illuminate\Database\Eloquent\ModelNotFoundException 的后续登录尝试失败

如何使用 API 中间件在 Laravel 中测试 PHPUnit 一个经过身份验证的用户?

尝试测试注册路由的 laravel 身份验证时

有没有办法在没有数据库的情况下使用 laravel 4.2 身份验证?

Laravel 未经身份验证无触发

如何使用 Laravel API 身份验证返回未授权