Framework: Using DatabaseMigrations trait prevents overriding console command dependencies

Created on 16 Sep 2016  路  4Comments  路  Source: laravel/framework

  • Laravel Version: 5.3.9
  • PHP Version: 7.0.7
  • Database Driver & Version: SQLite/MySQL

    Description:

It seems as though you can't swap out the IoC bindings for console commands when you're using the DatabaseMigrations trait in your tests.

For example, I've set up an default binding for and interface in AppServiceProvider . I also, have a console command which typehints the interface. During my test, I'm trying to swap out that dependency for a dummy one, but using $this->artisan('command') still uses the default binding. This is because I'm using DatabaseMigrations. Removing it allows me to swap out the binding like expected.

Steps To Reproduce:

Here's a example that demonstrates this behavior. You can also run the tests for this example repository: https://github.com/davidhemphill/console-bindings

In AppServiceProvider I've set up a binding like this.

$this->app->bind(GatewayInterface::class, ActualGateway::class);

And this is a test for a console command that uses GatewayInterface as a dependency.

<?php

use App\DummyGateway;
use App\GatewayInterface;
use Illuminate\Foundation\Testing\DatabaseMigrations;

class YellTest extends TestCase
{
    use DatabaseMigrations;

    function testExample()
    {
        // Swap out the binding...or so we think. Instead of the default
        // ActualGateway implementation we want to use DummyGateway
        $this->app->bind(GatewayInterface::class, DummyGateway::class);

        // Outputs App/DummyGateway proving our binding is correct
        // dd(app(GatewayInterface::class));

        // Console command uses GatewayInterface as a dependency
        // what is actually resolved is an instance of ActualGateway
        $this->artisan('gateway-stuff');

        // This will work however
       // $this->app[App\Console\Kernel::class]->call('gateway-stuff');
    }
}

Most helpful comment

Thanks you!

All 4 comments

I believe this is because $this->artisan('migrate') is being called by the DatabaseMigrations trait before your test method runs. This causes the application to resolve the Console\Kernel class before your test method. It seems then that when you call $this->artisan() in your test, that it is looking to the already resolved instance of $this->app rather than re-resolving the Kernel from the container... When you specifically ask the container to resolve the Kernel again however, it takes the new bindings into affect?

This is a good guess as to what is happening, but I don't have a solution by any means, just what I learned from a similar nightmare of an experience yesterday chasing a similar problem down.

When you use the DatabaseMigrations trait in tests, a call to $this->artisan('migrate') is made for every test, this call creates an instance of Illuminate\Console\Application and resolves all commands.

Inside any test if you try to swap any console command dependency using $this->app->bind() and then call the command via $this->artisan('command') the old concrete implementation will be called instead of the swapped dummy one.

The problem is that we build Illuminate\Console\Application too early, it resolves the commands using the original bindings and no way of re-resolving. It's a bit tricky to fix.

Thanks @themsaid and @taylorotwell. Nice work! That was LIGHTNING fast!

Thanks you!

Was this page helpful?
0 / 5 - 0 ratings