如何模拟 Laravel Auth 门面的给定方法

问题描述 投票:0回答:3

我想测试 Auth 外观,当调用

createUserProivder()
方法时,返回我的用户提供程序。

问题是,对于以下代码,注释掉部分后,AuthManager 仍然是原始的,而不是模拟的。 对于未注释的部分,我收到错误:

Mockery\Exception\BadMethodCallException : Method Mockery_2_Illuminate_Auth_AuthManager::validate() does not exist on this mock object

我不知道如何测试。

我想测试一种自定义守卫行为,当调用 Guard 的

validated()
方法时,它会调用 UserProvider,因此我需要模拟 Auth 外观,因为它是返回 User Provider 的。

public function testUserIsAuthenticatedWhenUserProviderFindsCredentialsMatch()
    {
        $userId = Uuid::uuid();
        $user = new User($userId);
        $userProvider = new UserProvider($user);

//        $this->partialMock(AuthManager::class, function ($mock) use ($userProvider) {
//            $mock->shouldReceive('createUserProvider')
//                ->once()
//                ->andReturn($userProvider);
//        });

        Auth::shouldReceive('createUserProvider')
           ->once()
           ->andReturn($userProvider);

        $result = $this->app['auth']->validate(['dummy' => 123]);

测试方法:

/**
     * @param array $credentials
     * @return bool
     */
    public function validate(array $credentials = []): bool
    {
        $this->user = $this->provider->retrieveByCredentials($credentials);

        return (bool)$this->user;
    }

服务提供商:

class LaravelServiceProvider extends AuthServiceProvider
{
    /**
     * Register any application authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        Auth::extend(
            'jwt',
            function ($app, $name, array $config) {
                $moduleConfig = $app['config'];

                return new JWTAuthGuard(
                    Auth::createUserProvider($config['provider']),
                    $this->app['request'],
                    new JWTHelper()
                );
            }
        );
    }
}
laravel testing integration-testing mockery laravel-facade
3个回答
0
投票

仅仅因为您创建了一个模拟类,并不意味着它会在服务容器中自动替换。身份验证管理器绑定为单例,因此您可以使用以下方法更新服务容器中的共享实例:

$mock = $this->partialMock(AuthManager::class, function ($mock) use ($userProvider) {
            $mock->shouldReceive('createUserProvider')
                ->once()
                ->andReturn($userProvider);
        });

$this->app->instance('auth', $mock);

$result = $this->app['auth']->validate(['dummy' => 123]);

...

0
投票

经过大量调试,我发现了一个可以做到这一点的点:

protected function getEnvironmentSetUp($app)
{
    $this->mockUserProvider($app);
}

protected function mockUserProvider($app)

{
    $userId = Uuid::uuid();
    $user = new User($userId);
    $userProvider = new UserProvider($user);

    $mock = Mockery::mock(AuthManager::class)->makePartial();
    $reflection = new ReflectionClass($mock);
    $reflection_property = $reflection->getProperty('app');
    $reflection_property->setAccessible(true);
    $reflection_property->setValue($mock, $app);

    $mock
        ->shouldReceive('createUserProvider')
        ->andReturn($userProvider);
    $app->instance('auth', $mock);
}

然而,另一种方法是在 Tests 目录中创建一个用于测试目的的 UserProvider:

class TestUserProvider extends AuthServiceProvider
{
    /**
     * Register any application authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        Auth::provider(
            'TestProvider',
            function ($app, array $config) {
                return new UserProvider();
            }
        );
    }
}

然后在测试文件中

/**
 * Define environment setup.
 *
 * @param Application $app
 * @return void
 * @noinspection PhpMissingParamTypeInspection
 */
protected function getEnvironmentSetUp($app)
{
    // Setup default database to use sqlite :memory:
    $app['config']->set('auth.defaults.guard', 'jwt');
    $app['config']->set(
        'auth.guards',
        [
            'jwt' => ['driver' => 'jwt', 'provider' => 'users'],
            'jwt2' => ['driver' => 'jwt', 'provider' => 'users']
        ]
    );
    $app['config']->set(
        'auth.providers',
        [
            'users' => ['driver' => 'TestProvider'],
        ]
    );
}

0
投票

我知道这已经过时了,但如果这个解决方案有效,有人可能会受益或提供反馈。

我需要 Auth 外观来实现自定义规则:

public function passes($attribute, $value)
{
    return Auth::user()->current_company === $value;
}

为了检查是否已通过,我们知道用户将返回用户模型或 \Illuminate\Contracts\Auth\Authenticatable 的实例。所以想为什么不像这样伪造外观:

public function testPasses()
{
    $fake = new class {
        function user() {
            return new User(['pulse_current_company' => 1]);
        }
    };
    Auth::swap($fake);

    $this->assertTrue($this->getRule()->passes('company_id', 1));
}

看起来效果很好而且很简单。我知道这并不完全是嘲笑,它实际上使用的是假的,但对我有用。

© www.soinside.com 2019 - 2024. All rights reserved.