Brian Kidd's avatar

Testing console command with $this->info causes error

I have a simple console command with the following handlemethod:

public function handle()
{
  foreach (Team::all() as $team) {
    $this->info("Adding missing default roles for team {$team->name} ...");
    Role::createDefaultRolesForTeam($team);
    $this->info("Missing roles added for team {$team->name}.");
  }

  return Command::SUCCESS;
}

When I run the sail test (I'm using Laravel Sail), I get this error: Call to a member function writeln() on null in vendor/laravel/framework/src/Illuminate/Console/Concerns/InteractsWithIO.php:306

The test executes fine if I remove the console write. Any suggestions? I've been googling for a while and can't find anything but maybe I'm not searching the right way. Thanks

0 likes
4 replies
LaryAI's avatar
Level 58

The error message suggests that the $this->info() method is trying to call the writeln() method on a null object. This is likely because the $this->output property is not being set properly in the test environment.

To fix this, you can manually set the $this->output property in your test case before calling the handle() method. Here's an example:

use Illuminate\Support\Facades\Artisan;
use Tests\TestCase;

class YourCommandTest extends TestCase
{
    public function testHandle()
    {
        $this->app->instance('Illuminate\Console\OutputStyle', $this->getMockOutput());

        Artisan::call('your-command');

        // Assert that the command ran successfully
        $this->assertEquals(0, Artisan::output());
    }

    protected function getMockOutput()
    {
        $output = $this->getMockBuilder('Symfony\Component\Console\Output\ConsoleOutput')
            ->disableOriginalConstructor()
            ->getMock();

        $output->expects($this->any())
            ->method('writeln')
            ->will($this->returnCallback(function ($input) {
                echo $input . "\n";
            }));

        return new \Illuminate\Console\OutputStyle($this->app->make('Illuminate\Contracts\Console\Kernel'), $output);
    }
}

This code sets up a mock output object that will be used by the command during testing. The getMockOutput() method creates a mock object that expects calls to the writeln() method and echoes the input to the console. The $this->app->instance() call sets the mock output object as the output for the command.

With this setup, you should be able to call $this->info() in your command's handle() method without causing any errors.

Brian Kidd's avatar

@LaryAI That didn't quite work. Getting error: TypeError: Illuminate\Console\OutputStyle::__construct(): Argument #1 ($input) must be of type Symfony\Component\Console\Input\InputInterface, App\Console\Kernel given

Brian Kidd's avatar
Brian Kidd
OP
Best Answer
Level 27

Okay, I did two things: First, I was instantiating the class and calling handle but changed that to $this->artisan(...). The test was still failing but once I added anexpectsOutput expectation on the $this->artisan command, the error stopped.

1 like
fupa's avatar

I’ve noticed that calling $this->artisan(...) actually runs the command, which makes it difficult to mock or isolate in tests. For example, if the Artisan command is something like dumping the database schema, running the test will still execute it for the real project environment if $this->artisan(...) is used. Do you know of another way to handle this scenario?

Please or to participate in this conversation.