1

I want to test that the Auth facade, when createUserProivder() method is called, returns my user provider.

The problem is that with the following code, with the commented out part, the AuthManager is still the original, not the mock. With the uncommented part, I get an error: Mockery\Exception\BadMethodCallException : Method Mockery_2_Illuminate_Auth_AuthManager::validate() does not exist on this mock object

I don't know how to test it.

I want to test a custom guard behavior, that calls the UserProvider when Guard's validated() method is called, and for that reason I need to mock the Auth facade, because it is the one that returns the 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]);

Method to test:

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

        return (bool)$this->user;
    }

Service provider:

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()
                );
            }
        );
    }
}
JorgeeFG
  • 5,651
  • 12
  • 59
  • 92

2 Answers2

0

Just because you created a mocked class, does not mean it is automatically replaced in the service container. The auth manager is bound as a singleton, so you can update the shared instance in the service container by using:

$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]);

...
Kurt Friars
  • 3,625
  • 2
  • 16
  • 29
  • The problem is that before the test, the app is instantiated, and so whatever you do after doesn't matter. I found a fix after a lot of debugging – JorgeeFG Aug 23 '20 at 21:07
  • @JorgeeFG the instance method replaces the binding in the app... – Kurt Friars Aug 23 '20 at 21:08
  • Yes I got you, but what I'm saying is that the app is booted before the test. So before you can run the mock. I had to mock it at the Test Set up level, not in the test itself – JorgeeFG Aug 23 '20 at 21:10
0

After a lot of debugging I found a point where I was able to do this:

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);
}

However another way is to just create a UserProvider for testing purposes inside the Tests directory:

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();
            }
        );
    }
}

Then in the Test file

/**
 * 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'],
        ]
    );
}
JorgeeFG
  • 5,651
  • 12
  • 59
  • 92