Изменить привязку конкретного класса по требованию в Laravel Service Container - PullRequest
0 голосов
/ 28 мая 2019

Как динамически изменить привязку конкретного класса.

Я пытаюсь протестировать ремесленную команду, которая использует внешний API.

class ConsumeApiCommand extends Command
{
    public function __construct(ClientInterface $client)
    {
        parent::__construct();

        $this->client = $client;
    }

    public function handle()
    {
        $api_response = $this->client->request('POST', 'http://external.api/resource');

        $response = json_decode($api_response);

        if(isset($response['error'])) {
            $this->error($response['error']);
        } else {
            $this->status($response['status']);
        }
    }
}

В настоящее время; Я могу подделать конкретный класс в моих тестах.

class FakeServiceProvider extends AppServiceProvider
{
    public function register(): void
    {
        $this->app->bind(ClientInterface::class, function () {
            return new class implements ClientInterface {
                public function request($method, $uri, $headers = [], $body = [])
                {
                    return json_encode(['status' => "You've reached us."]);
                }
            };
        });
    }
}

Passing.

public function test_can_consume_api_if_authenticated()
{
    $this->artisan('consume:api')
         ->expectsOutput("You've reached us.")
         ->assertExitCode(0);
}

В противном случае; возвращает изначально связанный класс ответа You've reached us.

public function test_cant_consume_api_if_not_authenticated()
{
    $this->app->bind(ClientInterface::class, function () {
        return new class implements ClientInterface {
            public function request($method, $uri, $headers = [], $body = [])
            {
                return json_encode(['error' => "Unauthorized."]);
            }
        };
    });

    $this->artisan('consume:api')
         ->expectsOutput("Unauthorized.")
         ->assertExitCode(0);
}

Можно ли таким образом добиться желаемого поведения? Или привязки контейнера службы не могут измениться в течение срока действия запроса?

Ответы [ 2 ]

1 голос
/ 28 мая 2019

Всегда разрешено привязывать новый конкретный класс к интерфейсам в Laravel. Проблема, которую я вижу здесь (и с которой я сталкивался в аналогичном сценарии в прошлом), заключается в том, что при bind() новом конкретном классе команда ремесленника уже была инициализирована (со старой привязкой).

В методе setUp() вашего тестового примера вы должны заново зарегистрировать команду, чтобы она взяла новую привязку из интерфейса.

Чтобы проверить, является ли это возможным решением, просто добавьте dump() в метод __construct() команды, а другой - в setUp вашего теста. Если я прав, вы должны увидеть сначала одну команду, а затем другую.

0 голосов
/ 28 мая 2019

Я просто оставляю фрагмент кода для тех, кому может быть интересно, как я решил это.

Исходя из ответа Леонардо , источник в вопросе в порядке, но мне пришлось вручную манипулировать требуемым интерфейсом, добавить его в команду и добавить команду в контейнер службы.

Мне не удалось динамически переопределить привязку.

use Illuminate\Container\Container;
use Illuminate\Contracts\Console\Kernel;

class ConsumeApiCommandTest extends TestCase
{
    public function test_can_consume_api_if_authenticated()
    {
        $this->artisan('consume:api')
             ->expectsOutput("You've reached us.")
             ->assertExitCode(0);
    }

    public function test_cant_consume_api_if_not_authenticated()
    {
        // Mock client interface.
        $mock = \Mockery::mock(ClientInterface::class);

        // Here you override the methods you want.
        $mock->shouldReceive('request')->once()
             ->andReturn(json_encode(['error' => "Unauthorized."]));

        $command = new ConsumeApiCommand($mock);

        // Re-register artisan command.
        Container::getInstance()->make(Kernel::class)->registerCommand($command);

        $this->artisan('consume:api')
             ->expectsOutput("Unauthorized.")
             ->assertExitCode(0);
    }
}
...