How do I change a concrete class binding dynamically.
I'm trying to test an artisan command that consumes an external 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']);
}
}
}
Currently; I can fake the concrete class in my tests.
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);
}
Failing; returns initially binded class response 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);
}
Is it possible to achieve the desire behavior this way? Or service container bindings can't change during the request lifetime?
Am just leaving a code snippet for those that might be wondering how I solved this.
Basing on Leonardo's answer, the source in the question is okay, but I had to manipulate the required interface manually, inject it into the command and add the command to the service container.
I was not able to override the binding dynamically.
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);
}
}