Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

MattApril's avatar

Laravel 11: asserting a command called another command

I have a command (MyCommand) that calls another third party (Passport) command by calling: $this->call('passport:purge');

I want to test my command and verify that the 'passport:purge' command gets called as well. I've had this test working for years with this:

		$purgeCommand = \Mockery::mock(PurgeCommand::class . '[handle]');
        $purgeCommand->shouldReceive('handle')->once();
        $this->app[Kernel::class]->registerCommand($purgeCommand);

		$this->artisan(MyCommand::class);

However, as of Laravel 11/Passport 12 it no longer works due to the introduction of lazy commands using the AsCommand attribute. I get the following error:

ArgumentCountError: Too few arguments to function Symfony\Component\Console\Attribute\AsCommand::__construct(), 0 passed in /var/www/auth/vendor/mockery/mockery/library/Mockery/Loader/EvalLoader.php(30) : eval()'d code on line 31 and at least 1 expected

I've tried the following unsuccessfully:

  • Different mock types: I was able to fix the above error, but I was unable to register my mock command.
  • CommandStarting event: according to Laravel docs this event should be fired when a command starts, but this does not seem to be the case when I used Event::fake()
  • Artisan::fake(): I think this caused a conflict when trying to execute the primary command from my test.
  • Queue the command using Artisan::queue(): this was the only thing that really worked, as I was able to check for the command using Queue::fake(), but I don't really want to queue the command other than for the sake of being able to test it.

Are there any other approaches that I've overlooked?

0 likes
2 replies
Amaury's avatar

@mattapril Hi, if you want to test against console events, you need to add the Illuminate\Foundation\Testing\WithConsoleEvents trait to your class test.

MattApril's avatar
MattApril
OP
Best Answer
Level 1

@Amaury Thanks, although that only seemed to trigger events for the main command that I was calling, not the sub commands. I did however finally find a solution:

function testPassportPurgeCommandCalled() {
		$artisanMock = \Mockery::mock(Artisan::getFacadeRoot())->makePartial();
        $artisanMock->shouldReceive('call')->once()->with('passport:purge');
        Artisan::swap($artisanMock);

        $this->artisan(MyCommand::class);
}

And make sure you use Artisan::call('passport:purge') inside of MyCommand (not $this->call('passport:purge'))

Please or to participate in this conversation.