Search code examples
phplaravelphpunitlaravel-9

Laravel 9: replace interface implementation in tests?


Here is an example of how I declared my configuration for MyService

public function register(): void
{
    $this->app->when(MyService::class)
        ->needs(IUser::class)
        ->give(fn (Application $app) => $app->make(ICompanyUserProvider::class)->getCompanyUser());
}

So I want to replace ICompanyUserProvider implementation for testing purposes.

I have already seen a solution like this

$this->app->bind(
    ICompanyUserProvider::class, 
    $this->app->runningUnitTests() ? ProviderStub::class : Provider::class
);

it works, but I find it inappropriate, I don't think it's a good practice to mix enviorenments in a single configuration.

Here is what else I tried so far:

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

abstract class TestCase extends BaseTestCase
{
    public function setUp(): void
    {
        parent::setUp();
     
        $this->app->bind(ICompanyUserProvider::class, ProviderStub::class);
    }
}

but it does not work (provider not being replaced with a stub one).

Any ideas, please?


Solution

  • Use app->instance to replace an instance in applicationContainer, here is an example:

    use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
    
    abstract class TestCase extends BaseTestCase
    {
        public function setUp(): void
        {
            parent::setUp();
    
            $this->app->instance(ICompanyUserProvider::class, new ProviderStub());
        }
    }
    

    If you look at the swap method from Facade class \Illuminate\Support\Facades\Facade::swap in line 182, they used the same method for replacing facade accessor with a fake instance.