'How to make Laravel's dependency injection work in PHPUnit unit test cases?

Some interfaces and implementations are mapped to be bound to each other in a service provider, however when I try to instantiate a class using Laravel's app() in the context of a unit test, these interface -> implementations don't seem to be registered.

class InterfaceServiceProvider extends ServiceProvider
{
    public const INTERFACE_BINDINGS = [
        \User\Repository::class => \Eloquent\User\Repository::class,
        \User\Role\Repository::class => \Eloquent\User\Role\Repository::class,
    ];

/**
 * Bootstrap services.
 *
 * @return void
 */
    public function boot()
    {
        $this->handleInterfaceBindings();
    }

    private function handleInterfaceBindings(): void
    {
        foreach (self::INTERFACE_BINDINGS as $interface => $implementation) {
            $this->app->bind($interface, $implementation);
        }
    }
}

This service provider is registered in config/app.php.

My test case below returns an error complaining about not being able to instantiate the repository (since I am injecting the interface as a dependency, and I want it to automatically inject the implementation everywhere):

class CheckerTest extends TestCase
{

    private \User\Permission\Checker $model;

    protected function setUp(): void
    {
        $this->model = app(\User\Permission\Checker::class);
    }

    public function testCheckerReturnsTrueWhenPermissionExists(): void
    {
        $permissions = new \User\Permission\Collection();
        $permissions->add(
            (new \User\Permission\Entity(1))
            ->setValue(
                \User\Permission::USER_VIEW->value
            )
        );
        $user = (new \User\Entity('1'))->setPermissions($permissions);

        $result = $this->model->check(
            \User\Permission::USER_VIEW,
            $user
        );

        $this->assertTrue($result);
    }
}

The error to be specific is:

Illuminate\Contracts\Container\BindingResolutionException

Target [User\Repository] is not instantiable while building [\User\Permission\Checker, User\Current].

Clearly the interface -> implementation binding that laravel provides doesnt work in the unit tests, how can I use it? Or is there something in PHPUnit I can use to get around this issue? I dont really want to mock each of the dependencies and inject them one by one as this is just one example but in some cases they go quite a few levels deep.



Solution 1:[1]

When you do setUp you have to call parent::setUp(); first...

class CheckerTest extends TestCase
{
    private \User\Permission\Checker $model;

    protected function setUp(): void
    {
        parent::setUp();

        $this->model = app(\User\Permission\Checker::class);
    }

    public function testCheckerReturnsTrueWhenPermissionExists(): void
    {
        $permissions = new \User\Permission\Collection();
        $permissions->add(
            (new \User\Permission\Entity(1))
            ->setValue(
                \User\Permission::USER_VIEW->value
            )
        );
        $user = (new \User\Entity('1'))->setPermissions($permissions);

        $result = $this->model->check(
            \User\Permission::USER_VIEW,
            $user
        );

        $this->assertTrue($result);
    }
}

See how the parent setUp it is still setting up stuff.


And, you are binding stuff in the boot method, you should be binding stuff in the register method. More info here.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 matiaslauriti