I am writing unit tests for interactive commands in Laravel 5.3 following this guide but I can't seem to get Mockery to work.
I've set up a simple command, greet:user.
public function handle()
{
if(!$name = $this->argument('name')) {
$name = $this->ask('Name of user to greet');
}
$this->info("Hello {$name}.");
}
And am writing unit-tests like;
public function testCanGreetGivenUser()
{
$command = Mockery::mock('App\Console\Commands\GreetUser[info]');
$command->shouldReceive('info')->once()->with('Hello Brian.');
$exit_code = Artisan::call('greet:user', ['name' => 'Brian', '--no-interaction' => true]);
$this->assertEquals(trim(Artisan::output()), 'Hello Brian.');
$this->assertEquals($exit_code, 0);
}
Issue:
Mockery\Exception\InvalidCountException: Method info("Hello Brian.") from Mockery_0_App_Console_Commands_GreetUser should be called
exactly 1 times but called 0 times.
My goal is to test;
If no input expected
$this->artisan('greet:user', ['name' => 'Brian'])
->expectsOutput('Hello Brian.')
->assertExitCode(0);
If input is required.
$this->artisan('greet:user')
->expectsQuestion('Name of user to greet', 'James')
->expectsOutput('Hello James.')
->assertExitCode(0);
That's how I could do it in Laravel 5.7, but how can I achieve the same for Laravel 5.3
To solve the problem you'll have to call the test as follows:
public function testCanGreetGivenUser()
{
$command = Mockery::mock('\App\Console\Commands\GreetUser[info]');
$command->shouldReceive('info')->once()->with('Hello Brian.');
$this->app[\Illuminate\Contracts\Console\Kernel::class]->registerCommand($command);
$exit_code = $this->artisan('greet:user', ['name' => 'Brian', '--no-interaction' => true]);
$this->assertEquals($exit_code, 0);
}
The deciding factor is the command registration. You must do it to actually replace the existing instance of the command with your mock.
Related
I was performing unit test in my application, and some of the functions include closure functions like below:
<?php
namespace JobProgress\Transformers;
use League\Fractal\TransformerAbstract;
class CustomersTransformer extends TransformerAbstract {
public function includesCreatedBy($customer) {
$user = $customer->createdBy;
if($user) {
return $this->item($user, function($user){
\Log::info('unit test');
return [
'id' => (int)$user->id,
'first_name' => $user->first_name,
'last_name' => $user->last_name,
'full_name' => $user->full_name,
'full_name_mobile' => $user->full_name_mobile,
'company_name' => $user->company_name,
];
});
}
}
}
Note : I have extended TransformerAbstract class of Fractal and using the item function as $this->item from the TransformerAbstract class.
Here is how i am executing my test:
public function testIncludeDeletedBy() {
$customer = factory(Customer::class)->create();
$include = $this->transformer->includesCreatedBy($customer);
$object = $include->getData()->first();
$this->assertInstanceOf(User::class, $object);
}
My unit test is not executing the code that i have written in closure function.
Like as i have mentioned in above code, i have added some logs but my unit test is not executing that portion of the code.
Can anyone please help me
This might be related to the implementation detail of the $this->item() method. You may expect the closure to be executed while as we can see, the closure only needs to be parsed and then passed to the method as a parameter.
The code shows nothing that it is actually executed and therefore you should not expect this to be as it is an implementation detail not under your test.
You could de-anonymize the closure and call it directly to be able to test it as a unit.
So you have one test for the if-clause in the existing method and one test which allows you to run (not only parse) the code you currently have as an anonymous function / closure.
This is sometimes called a test-point and can bring you up speed. The smaller the units you have are, the easier it is to test them.
<?php
namespace JobProgress\Transformers;
use League\Fractal\TransformerAbstract;
class CustomersTransformer extends TransformerAbstract {
public function includesCreatedBy($customer) {
$user = $customer->createdBy;
if($user) {
return $this->item($user, $this->transformImplementation(...));
}
}
public function transformImplementation(object $user) {
\Log::info('unit test');
return [
'id' => (int)$user->id,
'first_name' => $user->first_name,
'last_name' => $user->last_name,
'full_name' => $user->full_name,
'full_name_mobile' => $user->full_name_mobile,
'company_name' => $user->company_name,
];
}
}
For the three dots (...), this is PHP 8.1 syntax, compare PHP RFC: First-class callable syntax for alternative PHP syntax before 8.1 for callables, e.g.:
Closure::fromCallable([$this, 'transformImplementation'])
We can use below commands in laravel.
$user = Redis::get('user:profile:'.$id);
$values = Redis::lrange('names', 5, 10);
$values = Redis::command('lrange', ['name', 5, 10]);
but can't use memory usage keyname command with laravel redis facade.
Short answer no you can't execute. Let me explain;
public function createCommand($commandID, array $arguments = array())
{
$commandID = strtoupper($commandID);
if (!isset($this->commands[$commandID])) {
throw new ClientException("Command '$commandID' is not a registered Redis command.");
}
$commandClass = $this->commands[$commandID];
$command = new $commandClass();
$command->setArguments($arguments);
if (isset($this->processor)) {
$this->processor->process($command);
}
return $command;
}
this method is used when you invoke methods and it uses this abstract method to decide whether you can invoke a method or not.
abstract protected function getSupportedCommands();
getSupportedCommands method looks like this;
public function getSupportedCommands()
{
return array(
/* ---------------- Redis 1.2 ---------------- */
/* commands operating on the key space */
'EXISTS' => 'Predis\Command\KeyExists',
'DEL' => 'Predis\Command\KeyDelete',
'TYPE' => 'Predis\Command\KeyType',
'KEYS' => 'Predis\Command\KeyKeys',
'RANDOMKEY' => 'Predis\Command\KeyRandom',
'RENAME' => 'Predis\Command\KeyRename',
'RENAMENX' => 'Predis\Command\KeyRenamePreserve',
'EXPIRE' => 'Predis\Command\KeyExpire',
'EXPIREAT' => 'Predis\Command\KeyExpireAt',
'TTL' => 'Predis\Command\KeyTimeToLive',
'MOVE' => 'Predis\Command\KeyMove',
'SORT' => 'Predis\Command\KeySort',
'DUMP' => 'Predis\Command\KeyDump',
'RESTORE' => 'Predis\Command\KeyRestore',
....
...
..
memory usage command is not available in this method. You may check /vendor/predis/predis/src/Profile/RedisVersion300.php or any other class in Profile folder - it is not defined in there.
The reason is memory usage method is available since Redis version 4.0.0. This package is supporting commands until Redis version 3.0.0 as it can be seen from class names such as RedisVersion240, RedisVersion300 etc.
The command is not defined for that, but you can use eval:
Redis::connection('connection')->eval("return redis.call('memory', 'usage', 'keyname')", 0)
Or if you want to use the keyname as a paramater:
Redis::connection('connection')->eval("return redis.call('memory', 'usage', KEYS[1])", 1, 'keyname');
Redis LUA scripts are executed the same way in laravel
I am new to unit testing. I want to authenticate a user without using Factory. I want my testing code to be simple. I don't know how to use the Factory. Here is my code :
public function loginVerify()
{
$user = factory('App\User')->create();
}
the first thing that you have to do is follow the naming convention
Change
public function loginVerify()
{
$user = factory('App\User')->create();
}
to
public function testLoginVerify()
{
$user = factory('App\User')->create();
}
always use the test as a prefix for your testing function name.
and now as we look at your question, you can simply do this...
public function testLoginVerify()
{
$user_details = [
'email' => 'demo#gmail.com', // the email of a particular user
'password' => 'password' // the password of that user
];
$this->post('/login', $user_details)->assertRedirect('/home');
}
This is the very simplest way to do this.
I have to convert my unit tests to codeception. I need to use loginWithFakeUser() function from this article - How to mock authentication user on unit test in Laravel?
public function loginWithFakeUser() {
$user = new User([
'id' => 1,
'name' => 'yish'
]);
$this->be($user);
}
How can I use the $this->be() when my class is already extending \Codeception\Test\Unit? I don't know what should I use .. or how to use properly. Putting the function loginWithFakeUser() inside this:
use Illuminate\Foundation\Testing\Concerns\InteractsWithAuthentication;
use Illuminate\Foundation\Testing\Concerns\InteractsWithSession;
class AdminTest extends \Codeception\Test\Unit {
use InteractsWithAuthentication;
use InteractsWithSession;
}
Gives me an error:
[ErrorException] Undefined property: AdminTest::$app
I'm not sure how can I set the $app variable. Please help me. Thanks a lot!
I was able to solve this by mocking the Auth class.
$oUser = new User([
'id' => 1,
'name' => 'yish'
]);
Auth::shouldReceive('check')->once()->andReturn(true);
Auth::shouldReceive('user')->once()->andReturn($oUser);
Where in my actual code it uses it as:
if(Auth::check() === true) {
$sName = Auth::user()->name;
}
Trying to write a test for laravel php artisan command with ask() function. I have never used mockery before, but when i try to run test, it freezes, so i guess, i'm doing something wrong.
MyCommand.php:
public function handle()
{
$input['answer1'] = $this->ask('Ask question 1');
$input['answer2'] = $this->ask('Ask question 2');
$input['answer3'] = $this->ask('Ask question 3');
//--- processing validation
$validator = Validator::make($input, [
'answer1' => 'required',
'answer2' => 'required',
'answer3' => 'required',
]);
if ($validator->fails()) {
// processing error
}
} else {
// saving to DB
}
}
And my unit test:
$command = m::mock('\App\Console\Commands\Questions');
$command->shouldReceive('ask')
->andReturn('Answer 1')
->shouldReceive('ask')
->andReturn('Answer 2')
->shouldReceive('ask')
->andReturn('Answer 3')
$this->artisan('myCommand:toRun');
$this->assertDatabaseHas('myTable', [
'question1' => 'answer1'
]);
Laravel 5.4 - 5.6
The actual issue here is that running the console command is waiting for user input, however we are running this through PHPUnit so we are unable to enter anything.
Bumping up against limitations in unit testing can initially be frustrating, however limitations you find can end up being a blessing in disguise.
Currently, your implementation is tightly coupled to a view (a console command, so a view to an admin, but still a view none-the-less.) What could be done here is place any logic within a separate class which MyCommand can utilize, and which PHPUnit can actually test on their own. We know that the fundamentals of running a custom command work, as demonstrated in Laravel unit tests, so we can offload our logic in a separate, testable class.
Your new class might look something like this:
class CommandLogic
{
public function getQuestion1Text()
{
return 'Ask question 1';
}
public function getQuestion2Text()
{
return 'Ask question 2';
}
public function getQuestion3Text()
{
return 'Ask question 3';
}
public function submit(array $input)
{
$validator = \Illuminate\Support\Facades\Validator::make($input, [
'answer1' => 'required',
'answer2' => 'required',
'answer3' => 'required',
]);
if ($validator->fails()) {
// processing error
} else {
// saving to DB
}
}
}
...your actual unit test, something like this:
$commandLogic = new CommandLogic();
$sampleInput = [
'answer1' => 'test1',
'answer2' => 'test2',
'answer3' => 'test3',
];
$commandLogic->submit($sampleInput);
$this->assertDatabaseHas('myTable', [
'question1' => 'test1'
]);
...and your console command, something like this:
public function handle()
{
$commandLogic = new CommandLogic();
$input['answer1'] = $this->ask($commandLogic->getQuestion1Text());
$input['answer2'] = $this->ask($commandLogic->getQuestion2Text());
$input['answer3'] = $this->ask($commandLogic->getQuestion3Text());
$commandLogic->submit($input);
}
This enforces the single responsibility principle and separates the moving pieces in your codebase. I know this may feel like a bit of a cop out, but testing this stuff in Laravel 5.4 is tough. If you are willing to upgrade to 5.7 or higher, read below...
Laravel 5.7+
Laravel 5.7 introduced being able to run console tests, which satisfies the exact requirement this question is asking - https://laravel.com/docs/5.7/console-tests. This is more of a full integration test rather than a unit test.