通过 Laravel Dusk 测试模拟 Socialite 登录时出现 InvalidStateException

InvalidStateException when testing mocked Socialite login via Laravel Dusk

我在 Laravel 应用程序中使用 Socialite 以允许用户通过 Github 进行连接。

我的登录控制器包含以下两个方法:

    /**
     * GET /login/github
     * Redirect the user to the GitHub authentication page.
     */
    public function redirectToProvider()
    {
        return Socialite::driver('github')->redirect();
    }

    /**
     * GET /login/github/callback
     * Obtain the user information from GitHub.
     */
    public function handleProviderCallback(Request $request)
    {
        $githubUser = Socialite::driver('github')->user();

        // Actual login procedures go here; redacted for brevity

        return redirect('/'); 
    }

当我在浏览器中手动测试这些方法时,它们按预期工作。我访问 /login/github,在那里我被重定向到 Github 进行身份验证,然后我被送回 /login/github/callback?state=somelongrandomkey,然后我被重定向到主页 (/)。

我也在尝试通过 Laravel Dusk 来测试这些方法,嘲笑社会名流。

我的 Dusk 测试方法如下所示:

    public function testReceivesGithubRequestAndCreatesNewUser()
    {
        $this->browse(function (Browser $browser) {

            $user = factory('App\Models\User')->create([
                'github_token' => 'foobar',
                'github_username' => 'foobar'
            ]);

            # Mock 1 - A Socialite user
            $abstractUser = Mockery::mock('Laravel\Socialite\Two\User');

            # Mock 2 - Socialite's Github provider
            $provider = Mockery::mock('Laravel\Socialite\Contracts\Provider');
            $provider->shouldReceive('user')
                ->andReturn($abstractUser);

            # Mock 3 - Socialite
            Socialite::shouldReceive('driver')
                ->with('github')
                ->andReturn($provider);

            $browser->visit('/login/github/callback')->assertPathIs('/');
        });

当我 运行 这个测试时,对 /login/github/callback 的访问失败并显示 InvalidStateException

来自日志:

dusk.local.ERROR: {"exception":"[object] (Laravel\Socialite\Two\InvalidStateException(code: 0):  at /redacted/vendor/laravel/socialite/src/Two/AbstractProvider.php:210)
[stacktrace]
#0 /redacted/app/Http/Controllers/Auth/LoginController.php(84): Laravel\Socialite\Two\AbstractProvider->user()
[...etc...]

当我trace where the error is coming from in AbstractProvider 我看到它正在尝试将会话中的 state 与查询字符串中的 state 进行比较:

protected function hasInvalidState()
    {
        if ($this->isStateless()) {
            return false;
        }
        $state = $this->request->session()->pull('state');
        return ! (strlen($state) > 0 && $this->request->input('state') === $state);
    }

在我的 Dusk 测试中,当访问 /login/github/callback 时,查询字符串上没有任何状态,因此失败是合乎逻辑的。

我觉得我在设置提供该状态的模拟时缺少一些关键组件,但我不确定是什么。

我的测试是使用这两个示例构建的以供参考:

Dusk 测试与您提到的示例之间存在根本区别:Dusk 在实际浏览器中打开网站,因此测试和您的应用程序 运行 在不同的进程中。这就是模拟在 Dusk 测试中不起作用的原因。

这种集成测试背后的想法是模拟真实用户,无需模拟或任何其他 "shortcuts"。在您的情况下,这意味着使用真实的 GitHub 帐户登录。