Laravel Custom Helpers, Facades, and Testing Fakes
Let’s consider that we want to create a custom helper named SSH. This helper is going to connect to a remote server via ssh and execute some commands.
Commands
Since we might have many commands, I would create an interface first.
// app/Commands/Command.php
namespace App\SSHCommands;
use App\Contracts\SSHCommand;
interface Command
{
public function content();
}
And then an example command.
// app/Commands/DirectoryListCommand.php
namespace App\SSHCommands;
use App\Contracts\SSHCommand;
class DirectoryListCommand implements Command
{
public function content()
{
return "ls -la";
}
}
Helper
Alright. Now we want to create a Helper to use the commands. I rather create it in app/Helpers
path.
// app/Helpers/SSH.php
namespace App\Helpers;
use App\SSHCommands\Command;
class SSH
{
/**
* @param Command $command
* @return void
*/
public function exec($command)
{
//
}
}
Facade
To use this Helper in our application we’ll use Laravel’s Facades.
// app/Facades/SSH.php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
/**
* Class SSH
*
* @package App\Facades
*
* @method static exec($command)
*/
class SSH extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'ssh';
}
}
Now, Let’s register our Facade in the AppServiceProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
$this->app->bind('ssh', function () {
return new \App\Helpers\SSH;
});
}
}
Perfect! We can use the helper in the application by simply calling the facade.
\App\Facades\SSH::exec(new DirectoryListCommand());
To make it easy to use, You can register the SSH Facade in your config/app.php
under the aliases
'aliases' => [
...
'View' => Illuminate\Support\Facades\View::class,
...
],
Then you can use it like bellow
SSH::exec(new DirectoryListCommand());
Testing
When it comes to testing, The problem is you can’t really test the SSH because it is connecting to a remote server, and in the testing environment you don’t have a remote server! So what we can do?!
The answer is faking! We must fake the exec
command.
Let’s create a fake SSH class. I would like to create in app/Support/Testing
path.
namespace App\Support\Testing;
use PHPUnit\Framework\Assert as PHPUnit;
class SSHFake
{
/**
* @var \App\SSHCommands\Command
*/
protected $executedCommand;
/**
* @param \App\SSHCommands\Command $command
* @return void
*/
public function exec($command)
{
$this->executedCommand = get_class($command);
}
/**
* @param \App\SSHCommands\Command $command
* @return void
*/
public function assertExecuted($command)
{
if (!$this->executedCommand) {
PHPUnit::fail("No command executed");
}
PHPUnit::assertTrue(
true,
$this->executedCommand === $command
);
}
}
Let me explain the idea. I just simulated the SSH helper here with a small difference which means that I faked it. instead of executing the command on the remote server, I am adding the command’s class name to the executedCommand
property. And then, in the assertExecuted
method, I am making sure that the command is executed (stored in the executedCommand
property) 😊
The final step is to make the Facade recognize the faked Helper.
Let’s back to the SSH Facade and make a small change.
// app/Facades/SSH.php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
use App\Support\Testing\SSHFake;
/**
* Class SSH
*
* @package App\Facades
*
* @method static exec($command)
* @method static assertExecuted($command)
*/
class SSH extends Facade
{
/**
* Replace the bound instance with a fake.
*
* @return \App\Support\Testing\SSHFake
*/
public static function fake()
{
static::swap($fake = new SSHFake());
return $fake;
}
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'ssh';
}
}
I’ve created a static method named fake
which returns an instance of the faked one! I’ve used the swap method of the Facade facade 🙂
And now we can test the Helper very easily. Here is an example test:
namespace Tests\Feature;
use App\Facades\SSH;
use App\SSHCommands\DirectoryListCommand;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_ssh_command()
{
SSH::fake();
SSH::exec(new DirectoryListCommand());
SSH::assertExecuted(DirectoryListCommand::class);
}
}
That’s it! 🎉
Thanks for reading this post. Hope you enjoyed it!
I’ll be happy to read your comments about this.